Accept Merge Request #2008: (feature/tool_standard_library_process -> develop)
Merge Request: 外协流程更改 Created By: @廖丹龙 Reviewed By: @胡尧 Approved By: @胡尧 Accepted By: @廖丹龙 URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2008
This commit is contained in:
@@ -14,10 +14,12 @@
|
||||
<field name="name">原材料</field>
|
||||
<field name="type">原材料</field>
|
||||
</record>
|
||||
|
||||
<record id="product_category_surface_technics_sf" model="product.category">
|
||||
<field name="name">表面工艺</field>
|
||||
<field name="type">表面工艺</field>
|
||||
<field name="parent_id" ref="sf_manufacturing.product_category_outsource_process"/>
|
||||
<field name="property_cost_method">fifo</field>
|
||||
<field name="property_valuation">manual_periodic</field>
|
||||
</record>
|
||||
|
||||
<record id="product_category_cutting_tool_sf" model="product.category">
|
||||
@@ -40,10 +42,10 @@
|
||||
<!-- <field name="company_id" ref="base.main_company"/>-->
|
||||
</record>
|
||||
|
||||
<!-- <record id="res_users_bfm" model="res.users">-->
|
||||
<!-- <field name="name">业务平台</field>-->
|
||||
<!--<!– <field name="partner_id" ref="res_partner_bfm"/>–>-->
|
||||
<!-- </record>-->
|
||||
<!-- <record id="res_users_bfm" model="res.users">-->
|
||||
<!-- <field name="name">业务平台</field>-->
|
||||
<!--<!– <field name="partner_id" ref="res_partner_bfm"/>–>-->
|
||||
<!-- </record>-->
|
||||
|
||||
<record id="product_functional_tool_sf" model="product.product">
|
||||
<field name="name">功能刀具</field>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -9,8 +9,9 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing','jikimo_attachment_viewer'],
|
||||
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing', 'jikimo_attachment_viewer'],
|
||||
'data': [
|
||||
'data/sequence.xml',
|
||||
'data/stock_data.xml',
|
||||
'views/product_template_management_view.xml',
|
||||
],
|
||||
|
||||
10
sf_dlm_management/data/sequence.xml
Normal file
10
sf_dlm_management/data/sequence.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="sequence_production_process_parameter" model="ir.sequence">
|
||||
<field name="name">工艺可选参数编码序列</field>
|
||||
<field name="code">sf.production.process.parameter</field>
|
||||
<field name="prefix">WKSP</field>
|
||||
<field name="padding">9</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,2 +1,3 @@
|
||||
# from . import product_template
|
||||
# from . import product_supplierinfo
|
||||
from . import sf_production_common
|
||||
75
sf_dlm_management/models/sf_production_common.py
Normal file
75
sf_dlm_management/models/sf_production_common.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from odoo import fields, models, api
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import str2bool
|
||||
|
||||
|
||||
class SfProductionProcessParameter(models.Model):
|
||||
_inherit = 'sf.production.process.parameter'
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
|
||||
vals['code'] = self.env['ir.sequence'].next_by_code('sf.production.process.parameter') or '/'
|
||||
if not vals.get('process_id') and vals.get('routing_id'):
|
||||
vals['gain_way'] = '外协'
|
||||
routing_id = self.env['mrp.routing.workcenter'].browse(vals.get('routing_id'))
|
||||
if routing_id.surface_technics_id:
|
||||
vals['process_id'] = routing_id.surface_technics_id.id
|
||||
obj = super(SfProductionProcessParameter, self).create(vals)
|
||||
return obj
|
||||
def create_service_product(self):
|
||||
service_categ = self.env.ref(
|
||||
'sf_dlm.product_category_surface_technics_sf').sudo()
|
||||
|
||||
product_name = f"{self.process_id.name}{self.name}"
|
||||
product_id = self.env['product.template'].search(
|
||||
[("name", '=', product_name)])
|
||||
if product_id:
|
||||
product_id.server_product_process_parameters_id = self.id
|
||||
else:
|
||||
self.env['product.template'].create({
|
||||
'detailed_type': 'service',
|
||||
'name': product_name,
|
||||
'invoice_policy': 'delivery',
|
||||
'categ_id': service_categ.id,
|
||||
'description': f"基于{self.name}创建的服务产品",
|
||||
'sale_ok': True, # 可销售
|
||||
'purchase_ok': True, # 可采购
|
||||
'server_product_process_parameters_id': self.id,
|
||||
})
|
||||
|
||||
def create_work_center(self):
|
||||
production_process_parameter = self
|
||||
if not production_process_parameter.process_id:
|
||||
return
|
||||
if not production_process_parameter.routing_id:
|
||||
workcenter_id = self.env['mrp.routing.workcenter'].search(
|
||||
[("surface_technics_id", '=', production_process_parameter.process_id.id)])
|
||||
if not workcenter_id:
|
||||
outsourcing_work_center = self.env['mrp.workcenter'].search(
|
||||
[("name", '=', '外协工作中心')])
|
||||
routing_id = self.env['mrp.routing.workcenter'].create({
|
||||
'workcenter_ids': [(6, 0, outsourcing_work_center.ids)],
|
||||
'routing_tag': 'special',
|
||||
'routing_type': '表面工艺',
|
||||
'is_outsource': True,
|
||||
'surface_technics_id': production_process_parameter.process_id.id,
|
||||
'name': production_process_parameter.process_id.name,
|
||||
})
|
||||
production_process_parameter.routing_id = routing_id.id
|
||||
else:
|
||||
production_process_parameter.routing_id = workcenter_id.id
|
||||
|
||||
def init(self):
|
||||
super(SfProductionProcessParameter, self).init()
|
||||
# 在模块初始化时触发计算字段的更新
|
||||
records = self.search([])
|
||||
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_process',
|
||||
default='False')):
|
||||
return
|
||||
for record in records:
|
||||
if not record.outsourced_service_products:
|
||||
record.create_service_product()
|
||||
record.create_work_center()
|
||||
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_process', True)
|
||||
@@ -10,7 +10,8 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse','jikimo_attachment_viewer', 'jikimo_sale_multiple_supply_methods'],
|
||||
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse', 'jikimo_attachment_viewer',
|
||||
'jikimo_sale_multiple_supply_methods', 'product'],
|
||||
'data': [
|
||||
'data/cron_data.xml',
|
||||
'data/stock_data.xml',
|
||||
@@ -18,6 +19,7 @@
|
||||
'data/panel_data.xml',
|
||||
'data/sf_work_individuation_page.xml',
|
||||
'data/agv_scheduling_data.xml',
|
||||
'data/product_data.xml',
|
||||
'security/group_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/workpiece_delivery_views.xml',
|
||||
@@ -28,6 +30,7 @@
|
||||
'wizard/mrp_workorder_batch_replan_wizard_views.xml',
|
||||
'wizard/sf_programming_reason_views.xml',
|
||||
'wizard/sale_order_cancel_views.xml',
|
||||
'wizard/process_outsourcing.xml',
|
||||
'views/mrp_views_menus.xml',
|
||||
'views/agv_scheduling_views.xml',
|
||||
'views/stock_lot_views.xml',
|
||||
|
||||
23
sf_manufacturing/data/product_data.xml
Normal file
23
sf_manufacturing/data/product_data.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="product_category_service" model="product.category">
|
||||
<field name="name">服务</field>
|
||||
<field name="parent_id" ref="product.product_category_all"/>
|
||||
<field name="property_cost_method">fifo</field>
|
||||
<field name="property_valuation">manual_periodic</field>
|
||||
</record>
|
||||
<record id="product_category_outsource_process" model="product.category">
|
||||
<field name="name">工序外协</field>
|
||||
<field name="parent_id" ref="sf_manufacturing.product_category_service"/>
|
||||
<field name="property_cost_method">fifo</field>
|
||||
<field name="property_valuation">manual_periodic</field>
|
||||
</record>
|
||||
<record id="product_category_outsource_other_process" model="product.category">
|
||||
<field name="name">其他</field>
|
||||
<field name="parent_id" ref="sf_manufacturing.product_category_outsource_process"/>
|
||||
<field name="property_cost_method">fifo</field>
|
||||
<field name="property_valuation">manual_periodic</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -17,4 +17,5 @@ from . import sale_order
|
||||
from . import quick_easy_order
|
||||
from . import purchase_order
|
||||
from . import quality_check
|
||||
from . import purchase_request_line
|
||||
from . import workorder_printer
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
from operator import itemgetter
|
||||
|
||||
import requests
|
||||
from itertools import groupby
|
||||
@@ -889,11 +890,40 @@ class MrpProduction(models.Model):
|
||||
workorders_values.append(
|
||||
self.env[
|
||||
'mrp.workorder']._json_workorder_surface_process_str(
|
||||
production, route, product_production_process.seller_ids[0].partner_id.id))
|
||||
production, route, product_production_process.seller_ids[0].partner_id.id if product_production_process.seller_ids else False))
|
||||
production.workorder_ids = workorders_values
|
||||
for workorder in production.workorder_ids:
|
||||
workorder.duration_expected = workorder._get_duration_expected()
|
||||
|
||||
def _create_subcontract_purchase_request(self,purchase_request_line):
|
||||
sorted_list = sorted(purchase_request_line, key=itemgetter('name'))
|
||||
grouped_purchase_request_line = {
|
||||
k: list(g)
|
||||
for k, g in groupby(sorted_list, key=itemgetter('name'))
|
||||
}
|
||||
for name, request_line in grouped_purchase_request_line.items():
|
||||
request_line_sorted_list = sorted(request_line, key=itemgetter('product_id'))
|
||||
grouped_purchase_request_line_sorted_list = {
|
||||
k: list(g)
|
||||
for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id'))
|
||||
}
|
||||
purchase_request_model = self.env["purchase.request"]
|
||||
pr = purchase_request_model.create({
|
||||
"origin": name,
|
||||
"company_id": self.company_id.id,
|
||||
"picking_type_id": self.env.ref('stock.picking_type_in').id,
|
||||
"group_id": request_line[0].get('group_id'),
|
||||
"requested_by": self.env.context.get("uid", self.env.uid),
|
||||
"assigned_to": False,
|
||||
"bom_id":self[0].bom_id.id,
|
||||
})
|
||||
for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
|
||||
cur_request_line = request_line_list[0]
|
||||
cur_request_line['product_qty'] = len(request_line_list)
|
||||
cur_request_line['request_id'] = pr.id
|
||||
cur_request_line['origin'] = name
|
||||
cur_request_line.pop('group_id', None)
|
||||
self.env["purchase.request.line"].create(cur_request_line)
|
||||
# 外协出入库单处理
|
||||
def get_subcontract_pick_purchase(self):
|
||||
production_all = self.sorted(lambda x: x.id)
|
||||
@@ -903,6 +933,7 @@ class MrpProduction(models.Model):
|
||||
for product_id, pd in grouped_product_ids.items():
|
||||
product_id_to_production_names[product_id] = [p.name for p in pd]
|
||||
sorted_workorders = None
|
||||
purchase_request_line = []
|
||||
for production in production_all:
|
||||
proc_workorders = []
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
@@ -919,7 +950,9 @@ class MrpProduction(models.Model):
|
||||
return
|
||||
for workorders in reversed(sorted_workorders):
|
||||
self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
|
||||
self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
|
||||
# self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
|
||||
purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(workorders, production)
|
||||
self._create_subcontract_purchase_request(purchase_request_line)
|
||||
|
||||
# 工单排序
|
||||
def _reset_work_order_sequence1(self, k):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from odoo import fields, models, api
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import str2bool
|
||||
|
||||
|
||||
class ResMrpRoutingWorkcenter(models.Model):
|
||||
@@ -24,10 +25,29 @@ class ResMrpRoutingWorkcenter(models.Model):
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
|
||||
bom_id = fields.Many2one('mrp.bom', required=False)
|
||||
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
|
||||
optional_process_parameters = fields.One2many('sf.production.process.parameter','routing_id',string='可选工艺参数')
|
||||
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
||||
is_outsource = fields.Boolean('外协', default=False)
|
||||
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录')
|
||||
|
||||
@api.onchange('surface_technics_id')
|
||||
def optional_process_parameters_date(self):
|
||||
for record in self:
|
||||
if not record.surface_technics_id:
|
||||
continue
|
||||
parameter_ids = self.env['sf.production.process.parameter'].search([
|
||||
('process_id', '=', record.surface_technics_id.id),
|
||||
])
|
||||
record.optional_process_parameters = parameter_ids.ids
|
||||
|
||||
def init(self):
|
||||
super(ResMrpRoutingWorkcenter, self).init()
|
||||
# 在模块初始化时触发计算字段的更新
|
||||
records = self.search([])
|
||||
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')):
|
||||
return
|
||||
records.optional_process_parameters_date()
|
||||
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)
|
||||
def get_no(self):
|
||||
international_standards = self.search(
|
||||
[('code', '!=', ''), ('active', 'in', [True, False])],
|
||||
|
||||
@@ -21,7 +21,16 @@ class ResWorkcenter(models.Model):
|
||||
related='equipment_id.production_line_id', store=True)
|
||||
is_process_outsourcing = fields.Boolean('工艺外协')
|
||||
users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True)
|
||||
|
||||
@api.constrains('name')
|
||||
def _check_unique_name_code(self):
|
||||
for record in self:
|
||||
# 检查是否已经存在相同的 name 和 code 组合
|
||||
existing = self.search([
|
||||
('name', '=', record.name),
|
||||
('id', '!=', record.id) # 排除当前记录
|
||||
])
|
||||
if existing:
|
||||
raise ValueError('记录已存在')
|
||||
def write(self, vals):
|
||||
if 'users_ids' in vals:
|
||||
old_users = self.users_ids
|
||||
|
||||
@@ -106,11 +106,11 @@ class ResMrpWorkOrder(models.Model):
|
||||
record.back_button_display = False
|
||||
# tag_type
|
||||
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
|
||||
detection_result.processing_panel == cur_workorder.processing_panel and
|
||||
detection_result.routing_type == cur_workorder.routing_type and
|
||||
cur_workorder.tag_type !='重新加工' and
|
||||
detection_result.test_results != '合格'
|
||||
for detection_result in cur_workorder.production_id.detection_result_ids
|
||||
detection_result.processing_panel == cur_workorder.processing_panel and
|
||||
detection_result.routing_type == cur_workorder.routing_type and
|
||||
cur_workorder.tag_type != '重新加工' and
|
||||
detection_result.test_results != '合格'
|
||||
for detection_result in cur_workorder.production_id.detection_result_ids
|
||||
):
|
||||
record.back_button_display = False
|
||||
else:
|
||||
@@ -122,11 +122,11 @@ class ResMrpWorkOrder(models.Model):
|
||||
else:
|
||||
record.back_button_display = False
|
||||
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
|
||||
detection_result.processing_panel == cur_workorder.processing_panel and
|
||||
detection_result.routing_type == cur_workorder.routing_type and
|
||||
cur_workorder.tag_type !='重新加工' and
|
||||
detection_result.test_results != '合格'
|
||||
for detection_result in cur_workorder.production_id.detection_result_ids
|
||||
detection_result.processing_panel == cur_workorder.processing_panel and
|
||||
detection_result.routing_type == cur_workorder.routing_type and
|
||||
cur_workorder.tag_type != '重新加工' and
|
||||
detection_result.test_results != '合格'
|
||||
for detection_result in cur_workorder.production_id.detection_result_ids
|
||||
):
|
||||
record.back_button_display = False
|
||||
|
||||
@@ -429,9 +429,11 @@ class ResMrpWorkOrder(models.Model):
|
||||
def _compute_surface_technics_purchase_ids(self):
|
||||
for order in self:
|
||||
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
|
||||
domain = [('purchase_type', '=', 'consignment'),
|
||||
('origin', 'like', '%' + self.production_id.name + '%'),
|
||||
('state', '!=', 'cancel')]
|
||||
domain = [('group_id', '=', self.production_id.procurement_group_id.id),
|
||||
('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
|
||||
# domain = [('purchase_type', '=', 'consignment'),
|
||||
# ('origin', 'like', '%' + self.production_id.name + '%'),
|
||||
# ('state', '!=', 'cancel')]
|
||||
purchase = self.env['purchase.order'].search(domain)
|
||||
order.surface_technics_purchase_count = 0
|
||||
if not purchase:
|
||||
@@ -472,14 +474,15 @@ class ResMrpWorkOrder(models.Model):
|
||||
return result
|
||||
|
||||
def _get_surface_technics_purchase_ids(self):
|
||||
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
|
||||
purchase_orders = self.env['purchase.order'].search(domain)
|
||||
# domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
|
||||
domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
|
||||
purchase_orders = self.env['purchase.order'].search(domain, order='id desc', # 按创建时间降序(最新的在前)
|
||||
limit=1)
|
||||
purchase_orders_id = self.env['purchase.order']
|
||||
for po in purchase_orders:
|
||||
for line in po.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == self.surface_technics_parameters_id:
|
||||
if line.product_qty == 1:
|
||||
purchase_orders_id = line.order_id
|
||||
purchase_orders_id = line.order_id
|
||||
return purchase_orders_id
|
||||
|
||||
supplier_id = fields.Many2one('res.partner', string='外协供应商')
|
||||
@@ -1200,6 +1203,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
|
||||
}]
|
||||
return workorders_values_str
|
||||
|
||||
def _process_compute_state(self):
|
||||
for workorder in self:
|
||||
# 如果工单的工序没有进行排序则跳出循环
|
||||
@@ -1224,7 +1228,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
and workorder.production_id.schedule_state == '已排'
|
||||
and len(workorder.production_id.picking_ids.filtered(
|
||||
lambda w: w.state not in ['done', 'cancel'])) == 0):
|
||||
# and workorder.production_id.programming_state == '已编程'
|
||||
# and workorder.production_id.programming_state == '已编程'
|
||||
if workorder.is_subcontract is True:
|
||||
if workorder.production_id.state == 'rework':
|
||||
workorder.state = 'waiting'
|
||||
@@ -1287,6 +1291,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
mo.get_move_line(workorder.production_id, workorder))
|
||||
else:
|
||||
workorder.state = 'waiting'
|
||||
|
||||
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
|
||||
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
|
||||
'production_id.programming_state')
|
||||
@@ -1301,6 +1306,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
for check_id in workorder.check_ids:
|
||||
if not check_id.is_inspect:
|
||||
check_id.quality_state = 'none'
|
||||
|
||||
# 重写工单开始按钮方法
|
||||
def button_start(self):
|
||||
# 判断工单状态是否为等待组件
|
||||
@@ -1534,7 +1540,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
# workorder.rfid_code_old = rfid_code
|
||||
# workorder.rfid_code = False
|
||||
logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
|
||||
|
||||
|
||||
if is_production_id is True:
|
||||
logging.info('product_qty:%s' % record.production_id.product_qty)
|
||||
for move_raw_id in record.production_id.move_raw_ids:
|
||||
@@ -1552,7 +1558,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
# 如果工单包含了外协工序,需要预留数量
|
||||
if self.move_raw_ids.move_orig_ids.subcontract_workorder_id:
|
||||
location_id = self.move_raw_ids.location_id
|
||||
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(lambda q: q.location_id.id == location_id.id)
|
||||
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(
|
||||
lambda q: q.location_id.id == location_id.id)
|
||||
if quant.reserved_quantity == 0:
|
||||
self.env['stock.quant']._update_reserved_quantity(
|
||||
self.move_raw_ids.product_id,
|
||||
@@ -1706,7 +1713,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
store=True, string='工序作业')
|
||||
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录',
|
||||
related='routing_work_center_id.individuation_page_ids')
|
||||
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids', store=True)
|
||||
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids',
|
||||
store=True)
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_routing_work_center_id(self):
|
||||
@@ -1727,6 +1735,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
individuation_page_list = [item.code for item in mw.individuation_page_ids]
|
||||
if individuation_page_list:
|
||||
mw.individuation_page_list = list(set(individuation_page_list))
|
||||
|
||||
# =============================================================================================
|
||||
|
||||
is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False)
|
||||
@@ -2040,6 +2049,7 @@ class WorkPieceDelivery(models.Model):
|
||||
|
||||
def _get_agv_route_type_selection(self):
|
||||
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
|
||||
|
||||
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
|
||||
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
|
||||
status = fields.Selection(
|
||||
|
||||
@@ -51,7 +51,7 @@ class ResProductMo(models.Model):
|
||||
# domain="[('materials_id', '=', materials_id)]")
|
||||
# cutting_tool_model_id.material_model_id
|
||||
server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter',
|
||||
string='表面工艺参数(服务产品)')
|
||||
string='工艺参数(服务产品)')
|
||||
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel',
|
||||
string='表面工艺参数')
|
||||
|
||||
|
||||
@@ -66,6 +66,37 @@ class PurchaseOrder(models.Model):
|
||||
raise UserError('请对【产品】中的【数量】进行输入')
|
||||
if line.price_unit <= 0:
|
||||
raise UserError('请对【产品】中的【单价】进行输入')
|
||||
if record.purchase_type == 'consignment':
|
||||
bom_line_id = record.order_line[0].purchase_request_lines.request_id.bom_id.bom_line_ids
|
||||
replenish = self.env['stock.warehouse.orderpoint'].search([
|
||||
('product_id', '=', bom_line_id.product_id.id),
|
||||
(
|
||||
'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
|
||||
# ('state', 'in', ['draft', 'confirmed'])
|
||||
], limit=1)
|
||||
if not replenish:
|
||||
replenish_model = self.env['stock.warehouse.orderpoint']
|
||||
replenish = replenish_model.create({
|
||||
'product_id': bom_line_id.product_id.id,
|
||||
'location_id': self.env.ref(
|
||||
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
|
||||
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
|
||||
'group_id': record.group_id.id,
|
||||
'qty_to_order': 1,
|
||||
'origin': record.name,
|
||||
})
|
||||
else:
|
||||
replenish.write({
|
||||
'product_id': bom_line_id.product_id.id,
|
||||
'location_id': self.env.ref(
|
||||
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
|
||||
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
|
||||
'group_id': record.group_id.id,
|
||||
'qty_to_order': 1 + replenish.qty_to_order,
|
||||
'origin': record.name + ',' + replenish.origin,
|
||||
})
|
||||
replenish.action_replenish()
|
||||
|
||||
return super(PurchaseOrder, self).button_confirm()
|
||||
|
||||
|
||||
|
||||
27
sf_manufacturing/models/purchase_request_line.py
Normal file
27
sf_manufacturing/models/purchase_request_line.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import datetime
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
from operator import itemgetter
|
||||
|
||||
import requests
|
||||
from itertools import groupby
|
||||
from collections import defaultdict, namedtuple
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
|
||||
|
||||
|
||||
class PurchaseRequestLine(models.Model):
|
||||
_inherit = 'purchase.request.line'
|
||||
is_subcontract = fields.Boolean(string='是否外协')
|
||||
|
||||
|
||||
class PurchaseRequest(models.Model):
|
||||
_inherit = 'purchase.request'
|
||||
bom_id = fields.Many2one('mrp.bom')
|
||||
@@ -1,12 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from odoo import fields, models, api
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import str2bool
|
||||
|
||||
|
||||
class SfProductionProcessParameter(models.Model):
|
||||
_inherit = 'sf.production.process.parameter'
|
||||
outsourced_service_products = fields.One2many(
|
||||
'product.template', # 另一个模型的名称
|
||||
'server_product_process_parameters_id', # 对应的 Many2one 字段名称
|
||||
string='外协服务产品'
|
||||
)
|
||||
is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False)
|
||||
is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
|
||||
routing_id = fields.Many2one('mrp.routing.workcenter', string="工序")
|
||||
|
||||
@api.constrains('outsourced_service_products')
|
||||
def _validate_partner_limit(self):
|
||||
for record in self:
|
||||
if len(record.outsourced_service_products) > 1:
|
||||
raise ValidationError("工艺参数不能与多个产品关联")
|
||||
@api.depends('outsourced_service_products')
|
||||
def _compute_is_product_button(self):
|
||||
for record in self:
|
||||
if record.outsourced_service_products:
|
||||
record.is_product_button = True
|
||||
else:
|
||||
record.is_product_button = False
|
||||
|
||||
def has_wksp_prefix(self,code):
|
||||
"""
|
||||
判断字符串是否以WKSP开头(不区分大小写)
|
||||
:param text: 要检查的字符串
|
||||
:return: True/False
|
||||
"""
|
||||
return code.upper().startswith('WKSP')
|
||||
@api.depends('outsourced_service_products','code')
|
||||
def _compute_is_delete_button(self):
|
||||
for record in self:
|
||||
if record.outsourced_service_products and self.has_wksp_prefix(record.code):
|
||||
record.is_delete_button = False
|
||||
elif record.outsourced_service_products:
|
||||
record.is_delete_button = True
|
||||
else:
|
||||
record.is_delete_button = True
|
||||
@api.model
|
||||
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
||||
if self._context.get('route_id'):
|
||||
@@ -21,3 +59,33 @@ class SfProductionProcessParameter(models.Model):
|
||||
domain = [('process_id', '=', routing.surface_technics_id.id), ('id', 'not in', parameter)]
|
||||
return self._search(domain, limit=limit, access_rights_uid=name_get_uid)
|
||||
return super()._name_search(name, args, operator, limit, name_get_uid)
|
||||
|
||||
def action_create_service_product(self):
|
||||
if self.id: # 如果是已存在的记录
|
||||
self.write({}) # 空写入会触发保存
|
||||
else: # 如果是新记录
|
||||
self = self.create(self._convert_to_write(self.read()[0]))
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': '向导名称',
|
||||
'res_model': 'product.creation.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID
|
||||
}
|
||||
#
|
||||
# return {
|
||||
# 'name': '创建服务产品',
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# 'res_model': 'product.product',
|
||||
# 'view_mode': 'form',
|
||||
# 'view_id': self.env.ref('product.product_normal_form_view').id,
|
||||
# 'target': 'new', # 关键参数,使窗口以弹窗形式打开
|
||||
# 'context': {
|
||||
# 'default_' + k: v for k, v in default_values.items()
|
||||
# },
|
||||
# }
|
||||
|
||||
def action_hide_service_products(self):
|
||||
# self.outsourced_service_products.active = False
|
||||
self.active = False
|
||||
|
||||
@@ -193,4 +193,6 @@ access_sf_programming_record,sf_programming_record,model_sf_programming_record,b
|
||||
access_sf_work_individuation_page,sf_work_individuation_page,model_sf_work_individuation_page,sf_base.group_sf_mrp_user,1,1,1,0
|
||||
access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page_group_plan_dispatch,model_sf_work_individuation_page,sf_base.group_plan_dispatch,1,1,0,0
|
||||
access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
|
||||
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
|
||||
|
||||
access_product_creation_wizard,product_creation_wizard,model_product_creation_wizard,base.group_user,1,1,1,0
|
||||
|
@@ -22,6 +22,26 @@
|
||||
<field name="is_repeat"/>
|
||||
<field name="reserved_duration"/>
|
||||
</field>
|
||||
<xpath expr="//notebook/page[1]" position="before">
|
||||
<page string="可选工艺参数">
|
||||
<field name="optional_process_parameters">
|
||||
<tree editable="bottom">
|
||||
<field name="is_product_button" invisible="1"/>
|
||||
<field name="is_delete_button" invisible="1"/>
|
||||
<field name="code" attrs="{'readonly': True}"/>
|
||||
<field name="name"/>
|
||||
<field name="outsourced_service_products" widget="many2many_tags"/>
|
||||
<!-- 按钮列 -->
|
||||
<button name="action_create_service_product" string="创建服务产品" type="object"
|
||||
class="btn-primary"
|
||||
attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/>
|
||||
<button name="action_hide_service_products" string="删除" type="object"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible': [('is_delete_button', '=', True)]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
@@ -6,3 +6,4 @@ from . import production_technology_re_adjust_wizard
|
||||
from . import mrp_workorder_batch_replan_wizard
|
||||
from . import sf_programming_reason
|
||||
from . import sale_order_cancel
|
||||
from . import process_outsourcing
|
||||
42
sf_manufacturing/wizard/process_outsourcing.py
Normal file
42
sf_manufacturing/wizard/process_outsourcing.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class ProductCreationWizard(models.TransientModel):
|
||||
_name = 'product.creation.wizard'
|
||||
_description = '产品创建向导'
|
||||
|
||||
# 唯一需要用户输入的字段:产品类别
|
||||
categ_id = fields.Many2one(
|
||||
'product.category',
|
||||
string='产品类别',
|
||||
required=True,default=lambda self: self.env.ref(
|
||||
'sf_manufacturing.product_category_outsource_other_process',
|
||||
raise_if_not_found=False
|
||||
).sudo(),
|
||||
)
|
||||
process_parameter_id = fields.Many2one('sf.production.process.parameter')
|
||||
def action_create_service_product(self):
|
||||
# 获取产品分类(服务)
|
||||
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo()
|
||||
default_values = {
|
||||
'detailed_type': 'service',
|
||||
'name': f"{self.process_parameter_id.process_id.name}{self.process_parameter_id.name}",
|
||||
'invoice_policy': 'delivery',
|
||||
'categ_id': service_categ.id,
|
||||
'description': f"基于{self.process_parameter_id.name}创建的服务产品",
|
||||
'sale_ok': True, # 可销售
|
||||
'purchase_ok': True, # 可采购
|
||||
'server_product_process_parameters_id': self.process_parameter_id.id,
|
||||
}
|
||||
def action_create_product(self):
|
||||
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo()
|
||||
default_values = {
|
||||
'detailed_type': 'service',
|
||||
'name': f"{self.process_parameter_id.process_id.name}{self.process_parameter_id.name}",
|
||||
'invoice_policy': 'delivery',
|
||||
'categ_id': self.categ_id.id,
|
||||
'description': f"基于{self.process_parameter_id.name}创建的服务产品",
|
||||
'sale_ok': True, # 可销售
|
||||
'purchase_ok': True, # 可采购
|
||||
'server_product_process_parameters_id': self.process_parameter_id.id,
|
||||
}
|
||||
self.env['product.template'].create(default_values)
|
||||
37
sf_manufacturing/wizard/process_outsourcing.xml
Normal file
37
sf_manufacturing/wizard/process_outsourcing.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<odoo>
|
||||
<record id="view_product_creation_wizard_form" model="ir.ui.view">
|
||||
<field name="name">product.creation.wizard.form</field>
|
||||
<field name="model">product.creation.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="创建产品">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="categ_id" required="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button
|
||||
name="action_create_product"
|
||||
string="创建"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
/>
|
||||
<button
|
||||
string="取消"
|
||||
special="cancel"
|
||||
class="btn-secondary"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 向导动作 -->
|
||||
<record id="action_product_creation_wizard" model="ir.actions.act_window">
|
||||
<field name="name">快速创建产品</field>
|
||||
<field name="res_model">product.creation.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="product.model_product_product"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1135,8 +1135,10 @@ class sfProductionProcessParameter(models.Model):
|
||||
[("code", '=', item['code']), ('active', 'in', [True, False])])
|
||||
process = self.env['sf.production.process'].search(
|
||||
[('code', '=', item['process_id_code'])], limit=1)
|
||||
production_process_parameter = self.search(
|
||||
[("code", '=', item['code']), ('active', 'in', [True, False])])
|
||||
if not production_process_parameter:
|
||||
self.create({
|
||||
production_process_parameter = self.create({
|
||||
"name": item['name'],
|
||||
"process_description": item['process_description'],
|
||||
"processing_day": item['processing_day'],
|
||||
@@ -1148,6 +1150,7 @@ class sfProductionProcessParameter(models.Model):
|
||||
[('materials_no', 'in', item['materials_model_ids_codes'])]),
|
||||
'processing_mm': item['processing_mm']
|
||||
})
|
||||
production_process_parameter.create_service_product()
|
||||
else:
|
||||
production_process_parameter.name = item['name']
|
||||
production_process_parameter.process_description = item['process_description']
|
||||
@@ -1158,6 +1161,9 @@ class sfProductionProcessParameter(models.Model):
|
||||
[('materials_no', 'in', item['materials_model_ids_codes'])])
|
||||
production_process_parameter.active = item['active']
|
||||
production_process_parameter.processing_mm = item['processing_mm']
|
||||
if not production_process_parameter.outsourced_service_products:
|
||||
production_process_parameter.create_service_product()
|
||||
production_process_parameter.create_work_center()
|
||||
else:
|
||||
raise ValidationError("表面工艺可选参数认证未通过")
|
||||
|
||||
|
||||
@@ -343,6 +343,11 @@ class RePurchaseOrder(models.Model):
|
||||
if order_line.product_id.id in product_list:
|
||||
purchase.purchase_type = 'outsourcing'
|
||||
break
|
||||
request_lines = self.order_line.mapped('purchase_request_lines')
|
||||
# 检查是否存在 is_subcontract 为 True 的行
|
||||
if any(line.is_subcontract for line in request_lines):
|
||||
purchase.purchase_type = 'consignment'
|
||||
|
||||
|
||||
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
|
||||
string='交期状态', default='normal',
|
||||
@@ -376,6 +381,26 @@ class RePurchaseOrder(models.Model):
|
||||
if not line.taxes_id:
|
||||
raise UserError('请对【产品】中的【税】进行选择')
|
||||
|
||||
def get_purchase_request(self, consecutive_process_parameters, production):
|
||||
result = []
|
||||
for pp in consecutive_process_parameters:
|
||||
server_template = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
|
||||
('detailed_type', '=', 'service')])
|
||||
result.append({
|
||||
"product_id": server_template.product_variant_id.id,
|
||||
"name": production.procurement_group_id.name,
|
||||
"date_required": fields.Datetime.now(),
|
||||
"product_uom_id":server_template.uom_id.id,
|
||||
"product_qty": 1,
|
||||
"request_id": False,
|
||||
"move_dest_ids": False,
|
||||
"orderpoint_id": False,
|
||||
'is_subcontract':True,
|
||||
'group_id':production.procurement_group_id.id,
|
||||
})
|
||||
return result
|
||||
|
||||
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
|
||||
for pp in consecutive_process_parameters:
|
||||
server_product_process = []
|
||||
@@ -396,14 +421,14 @@ class RePurchaseOrder(models.Model):
|
||||
'manual_part_name': pp.part_name,
|
||||
}))
|
||||
# 获取服务商品最后一个供应商的采购员
|
||||
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id
|
||||
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id if server_template.seller_ids else False
|
||||
purchase_order = self.env['purchase.order'].sudo().create({
|
||||
'partner_id': server_template.seller_ids[0].partner_id.id,
|
||||
'partner_id': server_template.seller_ids[0].partner_id.id if server_template.seller_ids else False,
|
||||
'origin': production.name,
|
||||
'state': 'draft',
|
||||
'purchase_type': 'consignment',
|
||||
'order_line': server_product_process,
|
||||
'user_id': purchase_user_id.id
|
||||
'user_id': purchase_user_id.id if purchase_user_id else False,
|
||||
})
|
||||
purchase_order.order_line._compute_part_number()
|
||||
pp.purchase_id = [(6, 0, [purchase_order.id])]
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
'data/stock_location_data.xml',
|
||||
'views/stock_picking.xml',
|
||||
'views/stock_product_template.xml',
|
||||
],
|
||||
|
||||
13
sf_stock/data/stock_location_data.xml
Normal file
13
sf_stock/data/stock_location_data.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="stock_location_outsourcing_material_receiving_area" model="stock.location">
|
||||
<field name="name">外协收料区</field>
|
||||
<field name="usage">internal</field>
|
||||
</record>
|
||||
<record id="stock_route_process_outsourcing" model='stock.route'>
|
||||
<field name="name">工序外协</field>
|
||||
<field name="company_id"></field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import stock_picking
|
||||
from . import stock_picking
|
||||
from . import stock_warehouse_orderpoint
|
||||
11
sf_stock/models/stock_warehouse_orderpoint.py
Normal file
11
sf_stock/models/stock_warehouse_orderpoint.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import logging
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StockWarehouseOrderpoint(models.Model):
|
||||
_inherit = 'stock.warehouse.orderpoint'
|
||||
|
||||
origin = fields.Char('补货来源')
|
||||
Reference in New Issue
Block a user