diff --git a/jikimo_frontend/static/src/scss/custom_style.scss b/jikimo_frontend/static/src/scss/custom_style.scss index 8eb76259..6904dbac 100644 --- a/jikimo_frontend/static/src/scss/custom_style.scss +++ b/jikimo_frontend/static/src/scss/custom_style.scss @@ -536,3 +536,7 @@ div:has(.o_required_modifier) > label::before { position: unset; } +// 修复搜索面板checkbox样式 +.o_search_panel .form-check .form-check-label span { + position: relative; +} \ No newline at end of file diff --git a/jikimo_hide_options/models/models.py b/jikimo_hide_options/models/models.py index 8f30543c..6386192d 100644 --- a/jikimo_hide_options/models/models.py +++ b/jikimo_hide_options/models/models.py @@ -190,7 +190,7 @@ def _create(self, data_list): # 如果该用户组被限制创建或更新操作 if rec['is_create_or_update']: raise UserError( - _("You are restricted from performing this operation. Please contact the administrator.")) + _("您没有执行此操作的权限。请联系管理员")) else: # 如果 'access.right' 模型不存在,可以在这里定义备选逻辑 # 例如,记录日志、发送通知或者简单地跳过这部分逻辑 diff --git a/jikimo_sale_multiple_supply_methods/__init__.py b/jikimo_sale_multiple_supply_methods/__init__.py new file mode 100644 index 00000000..b02eeb6f --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/__init__.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +from . import models +from . import controllers + +from odoo import api, SUPERUSER_ID + +def _data_install(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + # 获取所有需要设置的产品模板 + env.ref('jikimo_sale_multiple_supply_methods.product_template_purchase').product_variant_id.write({'active': False, 'is_bfm': True}) + env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').product_variant_id.write({'active': False, 'single_manufacturing': True, 'is_bfm': True}) + env.ref('jikimo_sale_multiple_supply_methods.product_template_default').product_variant_id.write({'active': False, 'is_bfm': True}) + env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').product_variant_id.write({'active': False}) + env.ref('jikimo_sale_multiple_supply_methods.product_template_outsourcing').product_variant_id.write({'active': False, 'is_bfm': True}) + + # 为三步制造增加规则 + warehouse = env['stock.warehouse'].search([('company_id', '=', env.company.id)], limit=1) + product_route_id = warehouse.pbm_route_id + # 创建规则:原料存货区 -> 制造前, 坯料存货区 -> 制造前, 制造后 -> 坯料存货区, 制造后 -> 成品存货区 + raw_material_location_id = env['stock.location'].search([('name', '=', '坯料存货区')], limit=1) + picking_type_production = warehouse.pbm_type_id + picking_type_stock = warehouse.sam_type_id + material_location_id = env['stock.location'].search([('name', '=', '原料存货区')], limit=1) + # 为mto增加规则 + mto_route_id = env.ref('stock.route_warehouse0_mto') + # 创建规则:原料存货区 -> 外包位置, 坯料存货区 -> 外包位置 + subcontracting_location_id = env.company.subcontracting_location_id + picking_type_subcontracting = warehouse.subcontracting_resupply_type_id + # 为补给外包商增加规则 + resupply_route_id = warehouse.subcontracting_route_id + + rules_data = [ + { + 'name': 'WH: 原料存货区 → 制造前', + 'location_src_id': material_location_id.id, + 'location_dest_id': warehouse.pbm_loc_id.id, + 'route_id': product_route_id.id, + 'picking_type_id': picking_type_production.id, + 'action': 'pull', + 'sequence': 20, + 'warehouse_id': warehouse.id, + 'procure_method': 'mts_else_mto', + }, + { + 'name': 'WH: 坯料存货区 → 制造前', + 'location_src_id': raw_material_location_id.id, + 'location_dest_id': warehouse.pbm_loc_id.id, + 'route_id': product_route_id.id, + 'picking_type_id': picking_type_production.id, + 'action': 'pull', + 'sequence': 21, + 'warehouse_id': warehouse.id, + 'procure_method': 'mts_else_mto', + }, + { + 'name': 'WH: 制造后 → 坯料存货区', + 'location_src_id': warehouse.sam_loc_id.id, + 'location_dest_id': raw_material_location_id.id, + 'route_id': product_route_id.id, + 'picking_type_id': picking_type_stock.id, + 'action': 'push', + 'sequence': 23, + }, + { + 'name': 'WH: 制造后 → 成品存货区', + 'location_src_id': warehouse.sam_loc_id.id, + 'location_dest_id': env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id, + 'route_id': product_route_id.id, + 'picking_type_id': picking_type_stock.id, + 'action': 'push', + 'sequence': 24, + }, + { + 'name': 'WH: 原料存货区 → 外包位置 (MTO)', + 'location_src_id': material_location_id.id, + 'location_dest_id': subcontracting_location_id.id, + 'route_id': mto_route_id.id, + 'picking_type_id': picking_type_subcontracting.id, + 'action': 'pull', + 'sequence': 24, + 'warehouse_id': warehouse.id, + 'procure_method': 'mts_else_mto', + }, + { + 'name': 'WH: 坯料存货区 → 外包位置 (MTO)', + 'location_src_id': raw_material_location_id.id, + 'location_dest_id': subcontracting_location_id.id, + 'route_id': mto_route_id.id, + 'picking_type_id': picking_type_subcontracting.id, + 'action': 'pull', + 'sequence': 25, + 'warehouse_id': warehouse.id, + 'procure_method': 'mts_else_mto', + }, + { + 'name': 'WH: 坯料存货区 → 外包位置', + 'location_src_id': raw_material_location_id.id, + 'location_dest_id': subcontracting_location_id.id, + 'route_id': resupply_route_id.id, + 'picking_type_id': picking_type_subcontracting.id, + 'action': 'pull', + 'sequence': 26, + 'warehouse_id': warehouse.id, + 'procure_method': 'make_to_stock', + } + ] + # 遍历每个规则数据,执行创建或更新操作 + for rule_data in rules_data: + _create_or_update_stock_rule(env, rule_data) + +def _create_or_update_stock_rule(env, rule_data): + # 尝试查找现有的 stock.rule + existing_rule = env['stock.rule'].search([ + ('name', '=', rule_data['name']), + ('route_id', '=', rule_data.get('route_id')) + ], limit=1) + + if existing_rule: + # 如果存在,更新现有记录 + existing_rule.write(rule_data) + else: + # 如果不存在,创建新记录 + env['stock.rule'].create(rule_data) + +def _data_uninstall(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + warehouse = env['stock.warehouse'].search([('company_id', '=', env.company.id)], limit=1) + product_route_id = warehouse.pbm_route_id + resupply_route_id = warehouse.subcontracting_route_id + mto_route_id = env.ref('stock.route_warehouse0_mto') + # Fail unlink means that the route is used somewhere (e.g. route_id on stock.rule). In this case + # we don't try to do anything. + try: + with env.cr.savepoint(): + env['stock.rule'].search([('name', 'in', ('WH: 原料存货区 → 制造前', 'WH: 坯料存货区 → 制造前', 'WH: 制造后 → 坯料存货区', 'WH: 制造后 → 成品存货区')), ('route_id', '=', product_route_id.id)]).unlink() + env['stock.rule'].search([('name', 'in', ('WH: 原料存货区 → 外包位置 (MTO)', 'WH: 坯料存货区 → 外包位置 (MTO)')), ('route_id', '=', mto_route_id.id)]).unlink() + env['stock.rule'].search([('name', '=', 'WH: 坯料存货区 → 外包位置'), ('route_id', '=', resupply_route_id.id)]).unlink() + except: + pass \ No newline at end of file diff --git a/jikimo_sale_multiple_supply_methods/__manifest__.py b/jikimo_sale_multiple_supply_methods/__manifest__.py new file mode 100644 index 00000000..cfc56321 --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/__manifest__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +{ + 'name': '机企猫 多供货方式', + 'version': '16.0.1.0.0', + 'summary': """ 报价单提供(自动化产线加工/人工线下加工/外购/委外加工)多种供货方式选择 """, + 'author': 'fox', + 'website': '', + 'category': '', + 'depends': ['sf_dlm', 'sale_stock', 'sf_sale', 'sale'], + "data": [ + 'security/ir.model.access.csv', + 'data/stock_routes.xml', + 'data/product_data.xml', + # 'views/product_product_views.xml', + ],'assets': { + # 'web.assets_backend': [ + # 'jikimo_sale_multiple_supply_methods/static/src/**/*' + # ], + }, + 'post_init_hook': '_data_install', + 'uninstall_hook': '_data_uninstall', + 'application': True, + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/jikimo_sale_multiple_supply_methods/controllers/__init__.py b/jikimo_sale_multiple_supply_methods/controllers/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/controllers/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/jikimo_sale_multiple_supply_methods/data/product_data.xml b/jikimo_sale_multiple_supply_methods/data/product_data.xml new file mode 100644 index 00000000..b63da7ea --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/data/product_data.xml @@ -0,0 +1,98 @@ + + + + + 人工线下加工模板 + + + + delivery + product + false + + + + true + serial + + true + + + + 成品外购模板 + + + + serial + product + + + + + + + + 成品委外加工模板 + + + + serial + product + + + + + + + + 成品初始化模板 + + + + serial + product + + + + + + + + + + + + + 坯料客供料模板 + + + + false + serial + product + + + + + + + + + true + + + + + + + + + + + \ No newline at end of file diff --git a/jikimo_sale_multiple_supply_methods/data/stock_routes.xml b/jikimo_sale_multiple_supply_methods/data/stock_routes.xml new file mode 100644 index 00000000..de4c2106 --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/data/stock_routes.xml @@ -0,0 +1,32 @@ + + + + + 带料加工 + true + true + + 16 + + + + 客供料入库 + incoming + true + + DL + + + + + + + 带料收货 + + + + + pull + + + \ No newline at end of file diff --git a/jikimo_sale_multiple_supply_methods/models/__init__.py b/jikimo_sale_multiple_supply_methods/models/__init__.py new file mode 100644 index 00000000..0674dbe8 --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import product_template +from . import mrp_bom diff --git a/jikimo_sale_multiple_supply_methods/models/mrp_bom.py b/jikimo_sale_multiple_supply_methods/models/mrp_bom.py new file mode 100644 index 00000000..2528d0a4 --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/models/mrp_bom.py @@ -0,0 +1,13 @@ +from odoo import models, fields + +class MrpBom(models.Model): + _inherit = 'mrp.bom' + + # 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom + def bom_create(self, product, bom_type, product_type): + bom_id = super(MrpBom, self).bom_create(product, bom_type, product_type) + + # 成品的供应商从模板中获取 + if product_type == 'product': + bom_id.subcontractor_id = product.product_tmpl_id.seller_ids.partner_id.id + return bom_id diff --git a/jikimo_sale_multiple_supply_methods/models/product_template.py b/jikimo_sale_multiple_supply_methods/models/product_template.py new file mode 100644 index 00000000..640571f7 --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/models/product_template.py @@ -0,0 +1,26 @@ +from odoo import models, fields, api + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_manual_processing = fields.Boolean(string='人工线下加工') + is_customer_provided = fields.Boolean(string='客供料') + + def copy_template(self, product_template_id): + if not isinstance(product_template_id, ProductTemplate): + raise ValueError('%s必须是ProductTemplate类型' % product_template_id) + + self.route_ids = product_template_id.route_ids + self.categ_id = product_template_id.categ_id + self.invoice_policy = product_template_id.invoice_policy + self.detailed_type = product_template_id.detailed_type + self.purchase_ok = product_template_id.purchase_ok + self.uom_id = product_template_id.uom_id + self.uom_po_id = product_template_id.uom_po_id + self.company_id = product_template_id.company_id + self.single_manufacturing = product_template_id.single_manufacturing + self.tracking = product_template_id.tracking + self.is_bfm = product_template_id.is_bfm + self.is_manual_processing = product_template_id.is_manual_processing + # 复制 seller_ids + self.seller_ids = [(0, 0, {'partner_id': seller.partner_id.id, 'delay': 1.0}) for seller in product_template_id.seller_ids] diff --git a/jikimo_sale_multiple_supply_methods/security/ir.model.access.csv b/jikimo_sale_multiple_supply_methods/security/ir.model.access.csv new file mode 100644 index 00000000..3b276af9 --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/security/ir.model.access.csv @@ -0,0 +1,8 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_sale_order_group_production_engineer,sale.order_group_production_engineer,sale.model_sale_order,sf_base.group_production_engineer,1,1,0,0 +access_sale_order_line_group_production_engineer,sale_order_line_group_production_engineer,sale.model_sale_order_line,sf_base.group_production_engineer,1,1,0,0 +access_product_product_group_production_engineer,product_product_group_production_engineer,product.model_product_product,sf_base.group_production_engineer,1,0,0,0 +access_product_template_group_production_engineer,product_template_group_production_engineer,product.model_product_template,sf_base.group_production_engineer,1,0,0,0 +access_stock_picking_group_production_engineer,stock_picking_group_production_engineer,stock.model_stock_picking,sf_base.group_production_engineer,1,0,0,0 +access_stock_move_group_production_engineer,stock_move_group_production_engineer,stock.model_stock_move,sf_base.group_production_engineer,1,0,0,0 +access_mrp_bom_group_production_engineer,mrp_bom_group_production_engineer,mrp.model_mrp_bom,sf_base.group_production_engineer,1,0,0,0 \ No newline at end of file diff --git a/jikimo_sale_multiple_supply_methods/views/product_product_views.xml b/jikimo_sale_multiple_supply_methods/views/product_product_views.xml new file mode 100644 index 00000000..2c14d2b5 --- /dev/null +++ b/jikimo_sale_multiple_supply_methods/views/product_product_views.xml @@ -0,0 +1,16 @@ + + + + + view.product.template.form.inherit.sf + product.template + + + + + + + + + + \ No newline at end of file diff --git a/quality_control/views/quality_views.xml b/quality_control/views/quality_views.xml index fbd917b2..8e5af75a 100644 --- a/quality_control/views/quality_views.xml +++ b/quality_control/views/quality_views.xml @@ -203,7 +203,7 @@ Quality Alerts quality.alert - kanban,tree,form,pivot,graph,calendar + tree,kanban,form,pivot,graph,calendar

Create a new quality alert diff --git a/sf_base/models/base.py b/sf_base/models/base.py index 3d83cc96..d7a71e1e 100644 --- a/sf_base/models/base.py +++ b/sf_base/models/base.py @@ -402,3 +402,22 @@ class MachiningAccuracy(models.Model): name = fields.Char('一般公差', index=True) standard_tolerance = fields.Char(string="标准公差") sync_id = fields.Char('同步ID') + + +class ReSaleOrder(models.Model): + _inherit = 'sale.order' + + person_of_delivery = fields.Char('收货人') + telephone_of_delivery = fields.Char('电话号码') + address_of_delivery = fields.Char('联系地址') + + +class EmbryoRedundancy(models.Model): + _name = "sf.embryo.redundancy" + + code = fields.Char('编码', required=True) + name = fields.Char('名称', required=True) + long = fields.Float('长度(mm)', required=True) + width = fields.Float('宽度(mm)', required=True) + height = fields.Float('高度(mm)', required=True) + active = fields.Boolean('有效', default=True) diff --git a/sf_base/models/common.py b/sf_base/models/common.py index 95572631..4152c119 100644 --- a/sf_base/models/common.py +++ b/sf_base/models/common.py @@ -56,7 +56,7 @@ class MrsMaterialModel(models.Model): finish_machining = fields.Float("精加工Vc(m/min)") remark = fields.Text("备注") gain_way = fields.Selection( - [("自加工", "自加工"), ("外协", "外协"), ("采购", "采购")], + [("自加工", "自加工"), ("外协", "委外加工"), ("采购", "采购")], default="", string="获取方式") supplier_ids = fields.One2many('sf.supplier.sort', 'materials_model_id', string='供应商') active = fields.Boolean('有效', default=True) @@ -100,6 +100,7 @@ class MrsProductionProcess(models.Model): travel_day = fields.Float('路途天数/d') sequence = fields.Integer('排序') + # class MrsProcessingTechnology(models.Model): # _name = 'sf.processing.technology' # _description = '加工工艺' @@ -157,7 +158,9 @@ class MrsProductionProcessParameter(models.Model): for parameter in self: if parameter.process_id: name = parameter.process_id.name + '-' + parameter.name - result.append((parameter.id, name)) + else: + name = parameter.name + result.append((parameter.id, name)) return result # 获取表面工艺的获取方式 diff --git a/sf_base/security/group_security.xml b/sf_base/security/group_security.xml index b45a2dbd..5224519f 100644 --- a/sf_base/security/group_security.xml +++ b/sf_base/security/group_security.xml @@ -1,6 +1,6 @@ - + 质检岗 @@ -46,6 +46,11 @@ + + 工艺工程师 + + + 计划 @@ -65,7 +70,7 @@ 计划调度岗 - + diff --git a/sf_base/security/ir.model.access.csv b/sf_base/security/ir.model.access.csv index 34605cca..8dd4a023 100644 --- a/sf_base/security/ir.model.access.csv +++ b/sf_base/security/ir.model.access.csv @@ -251,3 +251,6 @@ access_sf_cutting_tool_type_group_plan_dispatch,sf_cutting_tool_type_group_plan_ access_sf_machining_accuracy,sf_machining_accuracy,model_sf_machining_accuracy,base.group_user,1,0,0,0 access_sf_machining_accuracy_admin,sf_machining_accuracy_admin,model_sf_machining_accuracy,base.group_system,1,0,0,0 + +access_sf_embryo_redundancy,sf_embryo_redundancy,model_sf_embryo_redundancy,base.group_user,1,0,0,0 +access_sf_embryo_redundancy_admin,sf_embryo_redundancy_admin,model_sf_embryo_redundancy,base.group_system,1,0,0,0 diff --git a/sf_base/static/src/scss/format_img.scss b/sf_base/static/src/scss/format_img.scss index 982d3c50..fc6f02dc 100644 --- a/sf_base/static/src/scss/format_img.scss +++ b/sf_base/static/src/scss/format_img.scss @@ -14,6 +14,7 @@ .img-fluid { max-width: unset !important; + width: 40px; } .o_inner_group .img-fluid { diff --git a/sf_base/views/base_view.xml b/sf_base/views/base_view.xml index eb2c1571..bd6f103b 100644 --- a/sf_base/views/base_view.xml +++ b/sf_base/views/base_view.xml @@ -633,4 +633,26 @@ sf.machining.accuracy tree + + #------------------坯料冗余量------------------ + + tree.sf.embryo.redundancy + sf.embryo.redundancy + + + + + + + + + + + + + 坯料冗余量 + ir.actions.act_window + sf.embryo.redundancy + tree + \ No newline at end of file diff --git a/sf_base/views/menu_view.xml b/sf_base/views/menu_view.xml index 4c662976..cf7c7aaf 100644 --- a/sf_base/views/menu_view.xml +++ b/sf_base/views/menu_view.xml @@ -141,18 +141,25 @@ sequence="1" action="action_sf_machine_brand"/> + + diff --git a/sf_bf_connect/controllers/controllers.py b/sf_bf_connect/controllers/controllers.py index 90c7e6c5..70a8d68f 100644 --- a/sf_bf_connect/controllers/controllers.py +++ b/sf_bf_connect/controllers/controllers.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import json import logging +import traceback + from odoo import http from odoo.http import request @@ -123,7 +125,8 @@ class Sf_Bf_Connect(http.Controller): res['factory_order_no'] = order_id.name return json.JSONEncoder().encode(res) except Exception as e: - logging.info('get_bfm_process_order_list error:%s' % e) + traceback_error = traceback.format_exc() + logging.error('get_bfm_process_order_list error: %s' % traceback_error) res['status'] = -1 res['message'] = '工厂创建销售订单和产品失败,请联系管理员' request.cr.rollback() diff --git a/sf_bf_connect/views/view.xml b/sf_bf_connect/views/view.xml index 184d0e71..f4245343 100644 --- a/sf_bf_connect/views/view.xml +++ b/sf_bf_connect/views/view.xml @@ -58,7 +58,7 @@ - + diff --git a/sf_dlm/data/product_data.xml b/sf_dlm/data/product_data.xml index 55c5d616..b8cab582 100644 --- a/sf_dlm/data/product_data.xml +++ b/sf_dlm/data/product_data.xml @@ -1,6 +1,6 @@ - + 坯料 坯料 diff --git a/sf_dlm/models/__init__.py b/sf_dlm/models/__init__.py index 6619bd58..f5b994b6 100644 --- a/sf_dlm/models/__init__.py +++ b/sf_dlm/models/__init__.py @@ -1 +1,3 @@ from . import product_supplierinfo +from . import stock_rule_inherit + diff --git a/sf_dlm/models/stock_rule_inherit.py b/sf_dlm/models/stock_rule_inherit.py new file mode 100644 index 00000000..4a40ca2f --- /dev/null +++ b/sf_dlm/models/stock_rule_inherit.py @@ -0,0 +1,87 @@ +import logging +from itertools import groupby +from odoo import models, fields, api, _ + + +class StockRuleInherit(models.Model): + _inherit = 'stock.rule' + + @api.model + def _run_buy(self, procurements): + # 判断补货组的采购类型 + procurements_group = {'standard': [], 'consignment': []} + for procurement, rule in procurements: + is_consignment = False + product = procurement.product_id + # 获取主 BOM + bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) + + if bom: + # 遍历 BOM 中的组件(即坯料等) + for line in bom.bom_line_ids: + raw_material = line.product_id + # 检查路线 + for route in raw_material.route_ids: + # print('route.name:', route.name) + if route.name == '按订单补给外包商': + is_consignment = True + + if is_consignment: + procurements_group['consignment'].append((procurement, rule)) + else: + procurements_group['standard'].append((procurement, rule)) + + for key, value in procurements_group.items(): + super(StockRuleInherit, self)._run_buy(value) + + if key == 'consignment': + for procurement, rule in value: + supplier = procurement.values.get('supplier') + if supplier: + domain = rule._make_po_get_domain(procurement.company_id, procurement.values, + supplier.partner_id) + logging.info("domain=============: %s", domain) + po = self.env['purchase.order'].sudo().search([ + ('partner_id', '=', supplier.partner_id.id), + ('company_id', '=', procurement.company_id.id), # 保证公司一致 + ('origin', 'like', procurement.origin), # 根据来源匹配 + ('state', '=', 'draft') # 状态为草稿 + ], limit=1) + logging.info("po=: %s", po) + if po: + po.write({'purchase_type': 'consignment'}) + + # # 首先调用父类的 _run_buy 方法,以保留原有逻辑 + # super(StockRuleInherit, self)._run_buy(procurements) + + # 然后在这里添加自定义的逻辑 + # for procurement, rule in procurements: + # product = procurement.product_id + # # 获取主 BOM + # bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) + + # if bom: + # # 遍历 BOM 中的组件(即坯料等) + # for line in bom.bom_line_ids: + # raw_material = line.product_id + # # 检查路线 + # for route in raw_material.route_ids: + # # print('route.name:', route.name) + # if route.name == '按订单补给外包商': # 或者用 route.id 检查精确的路线 + # print("按订单补给外包商============是") + # # 使用 procurement.values['supplier'] 获取供应商 + # supplier = procurement.values.get('supplier') + # if supplier: + # domain = rule._make_po_get_domain(procurement.company_id, procurement.values, + # supplier.partner_id) + # logging.info("domain=============: %s", domain) + # po = self.env['purchase.order'].sudo().search([ + # ('partner_id', '=', supplier.partner_id.id), + # ('company_id', '=', procurement.company_id.id), # 保证公司一致 + # ('origin', '=', procurement.origin), # 根据来源匹配 + # ('state', '=', 'draft') # 状态为草稿 + # ], limit=1) + # logging.info("po=: %s", po) + # if po: + # po.write({'purchase_type': 'consignment'}) + # break diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml index 1862e33c..1f3dc50b 100644 --- a/sf_dlm_management/views/product_template_management_view.xml +++ b/sf_dlm_management/views/product_template_management_view.xml @@ -2,6 +2,7 @@ + tree,kanban,form,activity {"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'} @@ -15,8 +16,10 @@ + + @@ -63,6 +66,9 @@ attrs="{'invisible': [('categ_type', '!=', '夹具')],'required': [('categ_type', '=', '夹具')]}" domain="[('fixture_model_id','=',fixture_model_id)]"/> + + + - - + + - - + + diff --git a/sf_machine_connect/controllers/controllers.py b/sf_machine_connect/controllers/controllers.py index ab03dff2..7eaac8aa 100644 --- a/sf_machine_connect/controllers/controllers.py +++ b/sf_machine_connect/controllers/controllers.py @@ -1264,6 +1264,7 @@ class Sf_Dashboard_Connect(http.Controller): """ 获取 """ + logging.info("kw=============:%s" % kw) res = {'status': 1, 'message': '成功', 'data': {}} # 连接数据库 conn = psycopg2.connect(**db_config) diff --git a/sf_machine_connect/views/WorkCenterBarcodes.xml b/sf_machine_connect/views/WorkCenterBarcodes.xml index 97fee70e..a1a5a5ea 100644 --- a/sf_machine_connect/views/WorkCenterBarcodes.xml +++ b/sf_machine_connect/views/WorkCenterBarcodes.xml @@ -10,6 +10,9 @@ + + + diff --git a/sf_maintenance/models/sf_maintenance.py b/sf_maintenance/models/sf_maintenance.py index 3d68bb9a..de6be8da 100644 --- a/sf_maintenance/models/sf_maintenance.py +++ b/sf_maintenance/models/sf_maintenance.py @@ -689,6 +689,8 @@ class SfMaintenanceEquipment(models.Model): if next_date < date_now: next_date = date_now else: + if not equipment.initial_action_date: + raise ValidationError('重置保养日期不能为空!!!') next_date = equipment.initial_action_date + timedelta(days=equipment.period) equipment.next_action_date = next_date else: @@ -735,6 +737,8 @@ class SfMaintenanceEquipment(models.Model): if next_date < date_now: next_date = date_now else: + if not equipment.initial_overhaul_date: + raise ValidationError('重置维修日期不能为空') next_date = equipment.initial_overhaul_date + timedelta(days=equipment.overhaul_period) equipment.overhaul_date = next_date else: diff --git a/sf_maintenance/models/sf_maintenance_oee.py b/sf_maintenance/models/sf_maintenance_oee.py index 0889fdb5..95dd74a4 100644 --- a/sf_maintenance/models/sf_maintenance_oee.py +++ b/sf_maintenance/models/sf_maintenance_oee.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import re import json +import logging import datetime import requests from odoo import api, fields, models, _ @@ -62,22 +63,22 @@ class SfMaintenanceEquipmentOEE(models.Model): ("封存(报废)", "封存(报废)")], default='正常', string="机床状态", related='equipment_id.state') - online_time = fields.Char('开机时长(小时)', reaonly='True') + online_time = fields.Char('开机时长(小时)', readonly='True') - offline_time = fields.Char('关机时长(小时)', reaonly='True') - idle_nums = fields.Integer('待机次数', reaonly='True') + offline_time = fields.Char('关机时长(小时)', readonly='True') + idle_nums = fields.Integer('待机次数', readonly='True') # 待机时长 - idle_time = fields.Char('待机时长(小时)', reaonly='True') + idle_time = fields.Char('待机时长(小时)', readonly='True') # 待机率 - idle_rate = fields.Char('待机率(%)', reaonly='True') + idle_rate = fields.Char('待机率(%)', readonly='True') - work_time = fields.Char('加工时长(小时)', reaonly='True') - work_rate = fields.Char('可用率(%)', reaonly='True') - fault_time = fields.Char('故障时长(小时)', reaonly='True') - fault_rate = fields.Char('故障率(%)', reaonly='True') - fault_nums = fields.Integer('故障次数', reaonly='True') + work_time = fields.Char('加工时长(小时)', readonly='True') + work_rate = fields.Char('可用率(%)', readonly='True') + fault_time = fields.Char('故障时长(小时)', readonly='True') + fault_rate = fields.Char('故障率(%)', readonly='True') + fault_nums = fields.Integer('故障次数', readonly='True') # 设备故障日志 sf_maintenance_logs_ids = fields.One2many('sf.maintenance.logs', 'maintenance_equipment_oee_id', '设备故障日志', @@ -90,11 +91,18 @@ class SfMaintenanceEquipmentOEE(models.Model): def get_running_datas(self): base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') - url_time = base_url + '/api/RunningTimeDetail' + logging.info("base_url=============:%s" % base_url) + # 只有当原始 URL 使用 http 时才替换为 https + if base_url.startswith("http://"): + secure_base_url = base_url.replace("http://", "https://") + else: + secure_base_url = base_url + url_time = secure_base_url + '/api/RunningTimeDetail' + logging.info("url_time=============:%s" % url_time) cnc_list_obj = self.env['maintenance.equipment'].sudo().search( [('function_type', '!=', False), ('active', '=', True)]) machine_list = list(map(lambda x: x.code, cnc_list_obj)) - # print('machine_list: %s' % machine_list) + logging.info("machine_list=============:%s" % machine_list) data_time = { "machine_list": str(machine_list) @@ -367,25 +375,25 @@ class SfMaintenanceEquipmentOEELog(models.Model): [("ZXJGZX", "钻铣加工中心"), ("CXJGZX", "车削加工中心"), ("FHJGZX", "复合加工中心")], default="", string="功能类型") machine_tool_picture = fields.Binary('设备图片') - type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', reaonly='True') + type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', readonly='True') state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"), ("检修", "检修"), ("保养", "保养")], default="", string="实时状态") - online_time = fields.Char('开机时长', reaonly='True') + online_time = fields.Char('开机时长', readonly='True') - offline_time = fields.Char('关机时长', reaonly='True') - offline_nums = fields.Integer('关机次数', reaonly='True') + offline_time = fields.Char('关机时长', readonly='True') + offline_nums = fields.Integer('关机次数', readonly='True') # 待机时长 - idle_time = fields.Char('待机时长', reaonly='True') + idle_time = fields.Char('待机时长', readonly='True') # 待机率 - idle_rate = fields.Char('待机率', reaonly='True') + idle_rate = fields.Char('待机率', readonly='True') - work_time = fields.Char('加工时长', reaonly='True') - work_rate = fields.Char('可用率', reaonly='True') - fault_time = fields.Char('故障时长', reaonly='True') - fault_rate = fields.Char('故障率', reaonly='True') - fault_nums = fields.Integer('故障次数', reaonly='True') + work_time = fields.Char('加工时长', readonly='True') + work_rate = fields.Char('可用率', readonly='True') + fault_time = fields.Char('故障时长', readonly='True') + fault_rate = fields.Char('故障率', readonly='True') + fault_nums = fields.Integer('故障次数', readonly='True') detail_ids = fields.One2many('maintenance.equipment.oee.log.detail', 'log_id', string='日志详情') diff --git a/sf_maintenance/views/maintenance_request_views.xml b/sf_maintenance/views/maintenance_request_views.xml index 9353e80e..b76326b8 100644 --- a/sf_maintenance/views/maintenance_request_views.xml +++ b/sf_maintenance/views/maintenance_request_views.xml @@ -139,7 +139,7 @@ 维保计划 maintenance.request tree,kanban,form,pivot,graph,calendar - + {'default_user_id': uid}

diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index fd15858a..991b26ab 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. { 'name': '机企猫智能工厂 制造管理', - 'version': '1.0', + 'version': '1.1', 'summary': '智能工厂制造模块', 'sequence': 1, 'description': """ @@ -10,8 +10,9 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse','jikimo_attachment_viewer'], + 'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse','jikimo_attachment_viewer', 'jikimo_sale_multiple_supply_methods'], 'data': [ + 'data/cron_data.xml', 'data/stock_data.xml', 'data/empty_racks_data.xml', 'data/panel_data.xml', @@ -21,6 +22,8 @@ 'wizard/workpiece_delivery_views.xml', 'wizard/rework_wizard_views.xml', 'wizard/production_wizard_views.xml', + 'wizard/production_technology_wizard_views.xml', + 'wizard/production_technology_re_adjust_wizard_views.xml', 'views/mrp_views_menus.xml', 'views/agv_scheduling_views.xml', 'views/stock_lot_views.xml', @@ -29,10 +32,12 @@ 'views/production_line_view.xml', 'views/mrp_workcenter_views.xml', 'views/mrp_workorder_view.xml', + 'views/stock_picking_view.xml', 'views/model_type_view.xml', 'views/agv_setting_views.xml', 'views/sf_maintenance_equipment.xml', 'views/res_config_settings_views.xml', + 'views/sale_order_views.xml', ], 'assets': { diff --git a/sf_manufacturing/controllers/__init__.py b/sf_manufacturing/controllers/__init__.py index 4d20cb33..4b95e252 100644 --- a/sf_manufacturing/controllers/__init__.py +++ b/sf_manufacturing/controllers/__init__.py @@ -1,2 +1,3 @@ from . import controllers from . import workpiece +from . import main diff --git a/sf_manufacturing/controllers/main.py b/sf_manufacturing/controllers/main.py new file mode 100644 index 00000000..e45265d3 --- /dev/null +++ b/sf_manufacturing/controllers/main.py @@ -0,0 +1,54 @@ +import logging +import json +import traceback + +from odoo import http +from odoo.http import request +from odoo.addons.sf_bf_connect.controllers.controllers import Sf_Bf_Connect + +_logger = logging.getLogger(__name__) + +class JikimoSaleRoutePicking(Sf_Bf_Connect): + + @http.route('/api/bfm_process_order/list', type='http', auth='sf_token', methods=['GET', 'POST'], csrf=False, + cors="*") + def get_bfm_process_order_list(self, **kw): + """ + 接收业务平台加工订单分配工厂时传送来的订单数据并生成销售订单和产品及坯料 + :param kw: + :return: + """ + res = {'status': 1, 'factory_order_no': ''} + # _logger.info('get_bfm_process_order_list:%s' % kw['order_number']) + try: + product_id = request.env.ref('jikimo_sale_multiple_supply_methods.product_template_default').with_context(active_test=False).sudo().product_variant_id + _logger.info('product_id:%s' % product_id) + company_id = request.env.ref('base.main_company').sudo() + bfm_process_order_list = json.loads(kw['bfm_process_order_list']) + order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create( + company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'], + kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], state='draft') + i = 1 + # 给sale_order的default_code字段赋值 + # aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)]) + # _logger.info('get_bfm_process_or===================================:%s' % order_id.name) + order_id.default_code = kw['order_number'] + if kw.get('logistics_way'): + order_id.logistics_way = kw['logistics_way'] + for item in bfm_process_order_list: + if item.get('embryo_redundancy_id'): + item['embryo_redundancy'] = request.env['sf.embryo.redundancy'].sudo().search([('code', '=', item['embryo_redundancy_id'])], limit=1) + item['embryo_redundancy_id'] = item['embryo_redundancy'].id + product = request.env['product.template'].sudo().product_create(product_id, item, order_id, + kw['order_number'], i) + product.product_tmpl_id.is_customer_provided = True if item['embryo_redundancy_id'] else False + order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item) + i += 1 + res['factory_order_no'] = order_id.name + except Exception as e: + traceback_error = traceback.format_exc() + logging.error('get_bfm_process_order_list error: %s' % traceback_error) + res['status'] = -1 + res['message'] = '工厂创建销售订单和产品失败,请联系管理员' + request.cr.rollback() + return json.JSONEncoder().encode(res) diff --git a/sf_manufacturing/data/cron_data.xml b/sf_manufacturing/data/cron_data.xml new file mode 100644 index 00000000..36e5bcf9 --- /dev/null +++ b/sf_manufacturing/data/cron_data.xml @@ -0,0 +1,29 @@ + + + + 工期状态变更 + + code + model._corn_update_construction_period_status() + 12 + hours + -1 + + + + + + + 交期状态变更 + + code + model._corn_update_delivery_status() + 12 + hours + -1 + + + + + + \ No newline at end of file diff --git a/sf_manufacturing/migrations/1.1/post-migrate.py b/sf_manufacturing/migrations/1.1/post-migrate.py new file mode 100644 index 00000000..0606049e --- /dev/null +++ b/sf_manufacturing/migrations/1.1/post-migrate.py @@ -0,0 +1,12 @@ +# migrations/1.1.0/post-migrate.py +from odoo import api, SUPERUSER_ID + +def migrate(cr, version): + # 获取环境 + env = api.Environment(cr, SUPERUSER_ID, {}) + + # 示例:添加新字段 + env.ref('sf_dlm.product_embryo_sf_self_machining').product_tmpl_id.write({'categ_type': '坯料'}) + env.ref('sf_dlm.product_template_sf').product_tmpl_id.write({'categ_type': '成品'}) + env.ref('sf_dlm.product_embryo_sf_outsource').product_tmpl_id.write({'categ_type': '坯料'}) + env.ref('sf_dlm.product_embryo_sf_purchase').product_tmpl_id.write({'categ_type': '坯料'}) \ No newline at end of file diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index 7d6aa8ae..9f77d841 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -11,3 +11,7 @@ from . import production_line_base from . import agv_setting from . import agv_scheduling from . import res_config_setting +from . import sf_technology_design +from . import sf_production_common +from . import sale_order +from . import quick_easy_order diff --git a/sf_manufacturing/models/model_type.py b/sf_manufacturing/models/model_type.py index 10157f63..1fe36b90 100644 --- a/sf_manufacturing/models/model_type.py +++ b/sf_manufacturing/models/model_type.py @@ -6,42 +6,29 @@ class ModelType(models.Model): _description = '模型类型' name = fields.Char('名称') - embryo_tolerance = fields.Integer('坯料容余') + # embryo_tolerance = fields.Char('坯料容余') + embryo_tolerance_id = fields.Many2one('sf.embryo.redundancy', string='坯料冗余') product_routing_tmpl_ids = fields.One2many('sf.product.model.type.routing.sort', 'product_model_type_id', - '成品工序模板') + '成品工序模板(自动化产线加工') embryo_routing_tmpl_ids = fields.One2many('sf.embryo.model.type.routing.sort', 'embryo_model_type_id', - '坯料工序模板') + '坯料工序模板(人工线下加工)') surface_technics_routing_tmpl_ids = fields.One2many('sf.surface_technics.model.type.routing.sort', 'surface_technics_model_type_id', '表面工艺工序模板') + manual_product_routing_tmpl_ids = fields.One2many('sf.manual.product.model.type.routing.sort', + 'manual_product_model_type_id', + '成品工序模板(人工线下加工)') class ProductModelTypeRoutingSort(models.Model): _name = 'sf.product.model.type.routing.sort' - _description = '成品工序排序' + _description = '成品工序排序(自动化产线加工)' sequence = fields.Integer('Sequence') route_workcenter_id = fields.Many2one('mrp.routing.workcenter', domain=[('routing_type', 'in', ['装夹预调', 'CNC加工', '解除装夹'])]) is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat') - - # routing_type = fields.Selection([ - # ('获取CNC加工程序', '获取CNC加工程序'), - # ('装夹', '装夹'), - # ('前置三元定位检测', '前置三元定位检测'), - # ('CNC加工', 'CNC加工'), - # ('后置三元质量检测', '后置三元质量检测'), - # ('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺') - # ], string="工序类型", compute='_compute_route_workcenter_id') - # - # @api.depends('route_workcenter_id') - # def _compute_route_workcenter_id(self): - # for record in self: - # if record: - # record.routing_type = record.route_workcenter_id.routing_type - routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type') - workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids') product_model_type_id = fields.Many2one('sf.model.type') @@ -57,24 +44,7 @@ class EmbryoModelTypeRoutingSort(models.Model): sequence = fields.Integer('Sequence') route_workcenter_id = fields.Many2one('mrp.routing.workcenter', domain=[('routing_type', 'in', ['切割'])]) is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat') - - # routing_type = fields.Selection([ - # ('获取CNC加工程序', '获取CNC加工程序'), - # ('装夹', '装夹'), - # ('前置三元定位检测', '前置三元定位检测'), - # ('CNC加工', 'CNC加工'), - # ('后置三元质量检测', '后置三元质量检测'), - # ('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺') - # ], string="工序类型", compute='_compute_route_workcenter_id') - # - # @api.depends('route_workcenter_id') - # def _compute_route_workcenter_id(self): - # for record in self: - # if record: - # record.routing_type = record.route_workcenter_id.routing_type - routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type') - workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids') embryo_model_type_id = fields.Many2one('sf.model.type') @@ -90,24 +60,7 @@ class SurfaceTechnicsModelTypeRoutingSort(models.Model): sequence = fields.Integer('Sequence') route_workcenter_id = fields.Many2one('mrp.routing.workcenter', domain=[('routing_type', 'in', ['表面工艺'])]) is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat') - - # routing_type = fields.Selection([ - # ('获取CNC加工程序', '获取CNC加工程序'), - # ('装夹', '装夹'), - # ('前置三元定位检测', '前置三元定位检测'), - # ('CNC加工', 'CNC加工'), - # ('后置三元质量检测', '后置三元质量检测'), - # ('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺') - # ], string="工序类型", compute='_compute_route_workcenter_id') - # - # @api.depends('route_workcenter_id') - # def _compute_route_workcenter_id(self): - # for record in self: - # if record: - # record.routing_type = record.route_workcenter_id.routing_type - routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type') - workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids') surface_technics_model_type_id = fields.Many2one('sf.model.type') @@ -116,3 +69,18 @@ class SurfaceTechnicsModelTypeRoutingSort(models.Model): 'route_model_type_uniq', 'unique (route_workcenter_id,surface_technics_model_type_id)', '表面工艺工序不能重复!') ] + +class ManualProductModelTypeRoutingSort(models.Model): + _name = 'sf.manual.product.model.type.routing.sort' + _description = '成品工序排序(人工线下加工)' + + sequence = fields.Integer('Sequence') + route_workcenter_id = fields.Many2one('mrp.routing.workcenter') + is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat') + routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type') + workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids') + manual_product_model_type_id = fields.Many2one('sf.model.type') + + _sql_constraints = [ + ('route_model_type_uniq', 'unique (route_workcenter_id,manual_product_model_type_id)', '成品工序不能重复!') + ] \ No newline at end of file diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 571971bc..f11a32ac 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -57,6 +57,95 @@ class MrpProduction(models.Model): else: production.deadline_of_delivery = False + def _compute_default_delivery_status(self): + try: + if self.state == 'cancel': + return False + if not self.deadline_of_delivery: + return False + hours = self.get_hours_diff() + if hours >= 48: + return '正常' + elif hours > 0 and hours < 48 and self.state != 'done': + return '预警' + elif hours > 0 and hours < 48 and self.state == 'done': + return '正常' + else: + return '已逾期' + except Exception as e: + logging.error("Error processing production ID {}: {}".format(self.id, e)) + raise e + + @api.depends('state', 'deadline_of_delivery') + def _compute_delivery_status(self): + for production in self: + delivery_status = production._compute_default_delivery_status() + if delivery_status and production.delivery_status != delivery_status: + production.delivery_status = delivery_status + + delivery_status = fields.Selection([('正常', '正常'), ('预警', '预警'), ('已逾期', '已逾期')], string='交期状态', + store=True, + compute='_compute_delivery_status', + default=lambda self: self._compute_default_delivery_status()) + + def get_page_all_records(self, model_name, func, domain, page_size=100): + # 获取模型对象 + model = self.env[model_name].sudo() + + # 初始化分页参数 + page_number = 1 + while True: + # 计算偏移量 + offset = (page_number - 1) * page_size + + # 获取当前页的数据 + records = model.search(domain, limit=page_size, offset=offset) + + # 如果没有更多记录,退出循环 + if not records: + break + + # 将当前页的数据添加到结果列表 + func(records) + # 增加页码 + page_number += 1 + + def run_compute_delivery_status(self, records): + records._compute_delivery_status() + + def _corn_update_delivery_status(self): + need_list = [ + 'draft', + 'technology_to_confirmed', + 'confirmed', + 'pending_cam', + 'progress', + 'rework', + 'scrap', + 'to_close', + ] + # previous_workorder = self.env['mrp.production'].search([('state', 'in', need_list)]) + self.get_page_all_records('mrp.production', self.run_compute_delivery_status, + [('state', 'in', need_list)], 100) + + def get_hours_diff(self): + # 获取当前日期和时间 + current_datetime = fields.Datetime.now() + + # 将 date_field 转换为 datetime 对象 + if self.deadline_of_delivery: + date_obj = fields.Date.from_string(self.deadline_of_delivery) + # 将 date 对象转换为 datetime 对象,设置时间为 00:00:00 + date_obj = datetime.datetime.combine(date_obj, datetime.time.min) + + # 计算两个日期之间的差值 + delta = date_obj - current_datetime + + # 返回差值的小时数 + return int(delta.total_seconds() / 3600) + else: + return 0.0 + @api.depends('workorder_ids.tool_state_remark') def _compute_tool_state_remark(self): for item in self: @@ -98,6 +187,7 @@ class MrpProduction(models.Model): # ]) state = fields.Selection([ ('draft', '草稿'), + ('technology_to_confirmed', '待工艺确认'), ('confirmed', '待排程'), ('pending_cam', '待加工'), ('progress', '加工中'), @@ -148,6 +238,8 @@ class MrpProduction(models.Model): quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True) + part_name = fields.Char(string='零件名称', related='product_id.part_name', readonly=True) + @api.depends('product_id.manual_quotation') def _compute_manual_quotation(self): for item in self: @@ -158,6 +250,8 @@ class MrpProduction(models.Model): is_remanufacture = fields.Boolean('是否重新制造', default=False) remanufacture_count = fields.Integer("重新制造订单数量", compute='_compute_remanufacture_production_ids') remanufacture_production_id = fields.Many2one('mrp.production', string='') + technology_design_ids = fields.One2many('sf.technology.design', 'production_id', string='工艺设计') + is_adjust = fields.Boolean('是否退回调整', default=False) @api.depends('remanufacture_production_id') def _compute_remanufacture_production_ids(self): @@ -184,7 +278,7 @@ class MrpProduction(models.Model): @api.depends( 'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state', - 'workorder_ids.state', 'product_qty', 'qty_producing', 'schedule_state') + 'workorder_ids.state', 'product_qty', 'qty_producing', 'schedule_state', 'programming_state', 'is_adjust') def _compute_state(self): for production in self: if not production.state or not production.product_uom_id: @@ -214,22 +308,33 @@ class MrpProduction(models.Model): precision_rounding=move.product_uom.rounding or move.product_id.uom_id.rounding) for move in production.move_raw_ids if move.product_id): production.state = 'progress' - - # # 新添加的状态逻辑 - if ( - production.state == 'to_close' or production.state == 'progress') and production.schedule_state == '未排': - production.state = 'confirmed' + # 新添加的状态逻辑 + if production.state in ['to_close', 'progress', + 'technology_to_confirmed'] and production.schedule_state == '未排': + if not production.workorder_ids or production.is_adjust is True: + production.state = 'technology_to_confirmed' + else: + if production.is_adjust is True: + production.state = 'technology_to_confirmed' + else: + production.state = 'confirmed' elif production.state == 'pending_cam' and production.schedule_state == '未排': production.state = 'confirmed' elif production.state == 'to_close' and production.schedule_state == '已排': production.state = 'pending_cam' - + elif production.state == 'confirmed' and production.is_adjust is True: + production.state = 'technology_to_confirmed' + if production.state == 'confirmed' and production.schedule_state == '已排': + production.state = 'pending_cam' if production.state == 'progress': if all(wo_state not in ('progress', 'done', 'rework', 'scrap') for wo_state in production.workorder_ids.mapped('state')): production.state = 'pending_cam' if production.is_rework is True: production.state = 'rework' + if (production.state == 'rework' and production.tool_state == '0' + and production.schedule_state == '已排' and production.is_rework is False): + production.state = 'pending_cam' # if production.state == 'pending_cam': # if all(wo_state in 'done' for wo_state in production.workorder_ids.mapped('state')): # production.state = 'done' @@ -252,6 +357,104 @@ class MrpProduction(models.Model): if production.tool_state == '2': production.state = 'rework' + # 退回调整 + def technology_back_adjust(self): + process_parameters = [] + domain = [('state', '=', 'confirmed'), ('origin', '=', self.origin)] + if self.production_type == '自动化产线加工': + cloud_programming = self._cron_get_programming_state() + if cloud_programming['send_state'] == 'sending': + raise UserError(_("编程文件正在下发中,请稍后重试")) + domain += [('programming_no', '=', self.programming_no)] + # 带排程的制造订单 + production_confirmed = self.env['mrp.production'].search(domain) + for special in production_confirmed.technology_design_ids: + if special.process_parameters_id: + product_production_process = self.env['product.template'].search( + [('server_product_process_parameters_id', '=', special.process_parameters_id.id)]) + if not product_production_process: + if special.process_parameters_id not in process_parameters: + process_parameters.append(special.process_parameters_id.display_name) + if process_parameters: + raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters))) + if production_confirmed: + return { + 'name': _('退回调整'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'sf.production.technology.re_adjust.wizard', + 'target': 'new', + 'context': { + 'default_production_id': self.id, + 'default_origin': self.origin, + }} + + # 工艺确认 + def technology_confirm(self): + process_parameters = [] + account_moves = [] + parameters_not = [] + special_design = self.technology_design_ids.filtered( + lambda a: a.routing_tag == 'special' and a.is_auto is False) + for special in special_design: + if special.route_id.routing_type == '表面工艺' and not special.process_parameters_id: + parameters_not.append(special.route_id.name) + if special.process_parameters_id: + product_production_process = self.env['product.template'].search( + [('server_product_process_parameters_id', '=', special.process_parameters_id.id)]) + if not product_production_process: + if special.process_parameters_id not in process_parameters: + process_parameters.append(special.process_parameters_id.display_name) + purchase = self.env['purchase.order'].search([('origin', '=', special.production_id.name)]) + account = self.env['account.move'].search([('id', 'in', purchase.invoice_ids)]) + if account.state not in ['cancel', False]: + if purchase.name not in account_moves: + account_moves.append(purchase.name) + if account_moves: + raise UserError(_("请联系工厂生产经理对采购订单为%s生成的账单进行取消", ", ".join(account_moves))) + if parameters_not: + raise UserError(_("【工艺设计】-【工序】为%s未选择参数,请选择", ", ".join(parameters_not))) + if process_parameters: + raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters))) + # 判断同一个加工面的标准工序的顺序是否依次排序 + error_panel = [] + technology_design = self.technology_design_ids.filtered(lambda a: a.routing_tag == 'standard').sorted( + key=lambda m: m.sequence) + for index, design in enumerate(technology_design): + routing_type = design.route_id.routing_type + if index < len(technology_design) - 1: + next_index = index + 1 + next_design = technology_design[next_index] + next_design_routing_type = next_design.route_id.routing_type + # logging.info('当前工序和加工面: %s-%s' % (design.route_id.name, design.panel)) + # logging.info('下一个工序和加工面: %s-%s' % (next_design.route_id.name, next_design.panel)) + if design.panel is not False: + if design.panel != next_design.panel: + if index == 0: + raise UserError('【加工面】为%s的标准工序里含有其他加工面的工序,请调整后重试' % design.panel) + if routing_type not in ['解除装夹']: + raise UserError('【加工面】为%s的标准工序顺序有误,请调整后重试' % design.panel) + if design.panel == next_design.panel: + if (routing_type == '装夹预调' and next_design_routing_type == '解除装夹') or ( + routing_type == 'CNC加工' and next_design_routing_type == '装夹预调'): + if design.panel not in error_panel: + error_panel.append(design.panel) + else: + if not error_panel and not process_parameters: + return { + 'name': _('工艺确认'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'sf.production.technology.wizard', + 'target': 'new', + 'context': { + 'default_production_id': self.id, + 'default_origin': self.origin, + }} + if error_panel: + raise UserError(_("【加工面】为%s的标准工序顺序有误,请调整后重试", ", ".join(error_panel))) + return True + def action_check(self): """ 审核启用 @@ -452,8 +655,8 @@ class MrpProduction(models.Model): if self.move_finished_ids.filtered(lambda m: m.product_id == self.product_id).move_line_ids: self.move_finished_ids.filtered( lambda m: m.product_id == self.product_id).move_line_ids.lot_id = self.lot_producing_id - if self.product_id.tracking == 'serial': - self._set_qty_producing() + # if self.product_id.tracking == 'serial': + # self._set_qty_producing() # 重载根据工序生成工单的程序:如果产品BOM中没有工序时, # 根据产品对应的模板类型中工序,去生成工单; @@ -489,122 +692,88 @@ class MrpProduction(models.Model): 'operation_id': operation.id, 'state': 'pending', }] - if production.product_id.categ_id.type == '成品': - # # 根据加工面板的面数及对应的工序模板生成工单 - i = 0 - processing_panel_len = len(production.product_id.model_processing_panel.split(',')) - for k in (production.product_id.model_processing_panel.split(',')): - product_routing_workcenter = self.env['sf.product.model.type.routing.sort'].search( - [('product_model_type_id', '=', production.product_id.product_model_type_id.id)], - order='sequence asc' - ) - i += 1 - for route in product_routing_workcenter: - if route.is_repeat is True: + if production.product_id.categ_id.type in ['成品', '坯料']: + # # 根据工序设计生成工单 + for route in production.technology_design_ids: + workorder_has = self.env['mrp.workorder'].search( + [('technology_design_id', '=', route.id), ('production_id', '=', production.id)]) + if not workorder_has: + if route.route_id.routing_type not in ['表面工艺']: workorders_values.append( - self.env['mrp.workorder'].json_workorder_str(k, production, route, item)) - # if i == processing_panel_len and route.routing_type == '解除装夹': - # workorders_values.append( - # self.env['mrp.workorder'].json_workorder_str(k, production, route)) - # 表面工艺工序 - # 获取表面工艺id - # 工序id - surface_technics_arr = [] - route_workcenter_arr = [] - for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: - if item.route_workcenter_id.surface_technics_id.id: - for process_param in production.product_id.model_process_parameters_ids: - logging.info('process_param:%s%s' % (process_param.id, process_param.name)) - if item.route_workcenter_id.surface_technics_id == process_param.process_id: - logging.info( - 'surface_technics_id:%s%s' % (item.route_workcenter_id.surface_technics_id.id, - item.route_workcenter_id.surface_technics_id.name)) - surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id) - route_workcenter_arr.append(item.route_workcenter_id.id) - if surface_technics_arr: - production_process = self.env['sf.production.process'].search( - [('id', 'in', surface_technics_arr)], - order='sequence asc' - ) - for p in production_process: - logging.info('production_process:%s' % p.name) - # if production_process: - process_parameter = production.product_id.model_process_parameters_ids.filtered( - lambda pm: pm.process_id.id == p.id) - if process_parameter: - # 产品为表面工艺服务的供应商 + self.env['mrp.workorder'].json_workorder_str(production, route)) + else: product_production_process = self.env['product.template'].search( - [('server_product_process_parameters_id', '=', process_parameter.id)]) - if product_production_process: - route_production_process = self.env[ - 'mrp.routing.workcenter'].search( - [('surface_technics_id', '=', p.id), - ('id', 'in', route_workcenter_arr)]) - if route_production_process: - workorders_values.append( - self.env[ - 'mrp.workorder']._json_workorder_surface_process_str( - production, route_production_process, - process_parameter, - product_production_process.seller_ids[0].partner_id.id)) - elif production.product_id.categ_id.type == '坯料': - embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search( - [('embryo_model_type_id', '=', production.product_id.embryo_model_type_id.id)], - order='sequence asc' - ) - for route in embryo_routing_workcenter: - workorders_values.append( - self.env['mrp.workorder'].json_workorder_str('', production, route)) + [('server_product_process_parameters_id', '=', route.process_parameters_id.id)]) + workorders_values.append( + self.env[ + 'mrp.workorder']._json_workorder_surface_process_str( + production, route, product_production_process.seller_ids[0].partner_id.id)) production.workorder_ids = workorders_values - # for production_item in productions: - process_parameter_workorder = self.env['mrp.workorder'].search( - [('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id), - ('is_subcontract', '=', True)]) - if process_parameter_workorder: - is_pick = False - consecutive_workorders = [] - m = 0 - sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id) - for i in range(len(sorted_workorders) - 1): - if m == 0: - is_pick = False - if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \ - sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \ - sorted_workorders[i].id == sorted_workorders[i + 1].id - 1: - if sorted_workorders[i] not in consecutive_workorders: - consecutive_workorders.append(sorted_workorders[i]) - consecutive_workorders.append(sorted_workorders[i + 1]) - m += 1 - continue - else: - if m == len(consecutive_workorders) - 1 and m != 0: - self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, - production) - if sorted_workorders[i] in consecutive_workorders: - is_pick = True - consecutive_workorders = [] - m = 0 - # 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单 - if is_pick is False: - self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], - production) - if m == len(consecutive_workorders) - 1 and m != 0: - self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, - production) - if sorted_workorders[i] in consecutive_workorders: - is_pick = True - consecutive_workorders = [] - m = 0 - if m == len(consecutive_workorders) - 1 and m != 0: - self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production) - if is_pick is False and m == 0: - if len(sorted_workorders) == 1: - self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production) - else: - self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production) for workorder in production.workorder_ids: workorder.duration_expected = workorder._get_duration_expected() + # 外协出入库单处理 + def get_subcontract_pick_purchase(self): + production_all = self.sorted(lambda x: x.id) + product_id_to_production_names = {} + grouped_product_ids = {k: list(g) for k, g in + groupby(production_all, key=lambda x: x.product_id.id)} + for product_id, pd in grouped_product_ids.items(): + product_id_to_production_names[product_id] = [p.name for p in pd] + for production in production_all: + proc_workorders = [] + process_parameter_workorder = self.env['mrp.workorder'].search( + [('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id), + ('is_subcontract', '=', True), ('state', '!=', 'cancel')], order='sequence asc') + if process_parameter_workorder: + # 将这些特殊表面工艺工单的采购单与调拨单置为失效 + for workorder in process_parameter_workorder: + workorder._get_surface_technics_purchase_ids().write({'state': 'cancel'}) + workorder.move_subcontract_workorder_ids.write({'state': 'cancel'}) + workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'cancel'}) + consecutive_workorders = [] + sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.sequence) + for i, workorder in enumerate(sorted_workorders): + # 检查当前工作订单和下一个工作订单是否连续,并且供应商相同 + if i == 0: + consecutive_workorders.append(workorder) + elif workorder.sequence == sorted_workorders[ + i - 1].sequence + 1 and workorder.supplier_id.id == sorted_workorders[i - 1].supplier_id.id: + consecutive_workorders.append(workorder) + else: + # 处理连续组,如果它不为空 + if consecutive_workorders: + proc_workorders.append(consecutive_workorders) + # 创建外协出入库单和采购订单 + # self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production, sorted_workorders) + # self.env['purchase.order'].get_purchase_order(consecutive_workorders, production, + # product_id_to_production_names) + if i < len(sorted_workorders) - 1: + # 重置连续组,并添加当前工作订单 + consecutive_workorders = [workorder] + else: + # 判断最后一笔: + if workorder.sequence == sorted_workorders[ + i - 1].sequence and workorder.supplier_id.id == sorted_workorders[ + i - 1].supplier_id.id: + consecutive_workorders = [workorder] + else: + proc_workorders.append([workorder]) + # 立即创建外协出入库单和采购订单 + # self.env['stock.picking'].create_outcontract_picking(workorder, production) + # self.env['purchase.order'].get_purchase_order(workorder, production, + # product_id_to_production_names) + consecutive_workorders = [] + + # 处理最后一个组,即使它可能只有一个工作订单 + if consecutive_workorders: + proc_workorders.append(consecutive_workorders) + # self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production) + # self.env['purchase.order'].get_purchase_order(consecutive_workorders, production, + # product_id_to_production_names) + for workorders in reversed(proc_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) # 工单排序 def _reset_work_order_sequence1(self, k): for rec in self: @@ -671,7 +840,84 @@ class MrpProduction(models.Model): self._reset_work_order_sequence1(k) return True + # 需对不连续工单对应的采购单和外协出入库单做处理 + def _reset_subcontract_pick_purchase(self): + production_all = self.sorted(lambda x: x.id) + product_id_to_production_names = {} + grouped_product_ids = {k: list(g) for k, g in + groupby(production_all, key=lambda x: x.product_id.id)} + for product_id, pd in grouped_product_ids.items(): + product_id_to_production_names[product_id] = [p.name for p in pd] + for item in production_all: + production_process = product_id_to_production_names.get(item.product_id.id) + workorder_sf = item.workorder_ids.filtered(lambda sf: sf.routing_type == '表面工艺') + for i, workorder in enumerate(workorder_sf): + if i == 0: + continue + elif workorder.sequence != workorder_sf[i - 1].sequence + 1: + # workorder.picking_ids.move_ids = False + workorder.picking_ids = False + purchase_order = self.env['purchase.order'].search( + [('state', '=', 'draft'), ('origin', '=', item.name), + ('purchase_type', '=', 'consignment')]) + server_template = self.env['product.template'].search( + [('server_product_process_parameters_id', '=', + workorder.surface_technics_parameters_id.id), + ('detailed_type', '=', 'service')]) + for po in purchase_order: + for line in po.order_line: + if line.product_id == server_template.product_variant_id: + continue + if server_template.server_product_process_parameters_id != line.product_id.server_product_process_parameters_id: + purchase_order_line = self.env['purchase.order.line'].search( + [('product_id', '=', server_template.product_variant_id.id), ('id', '=', line.id), + ('product_qty', '=', 1)], limit=1, order='id desc') + if purchase_order_line: + line.unlink() + def _reset_work_order_sequence(self): + """ + 工单工序排序方法(新) + """ + for rec in self: + workorder_ids = rec.workorder_ids + technology_design_ids = rec.technology_design_ids + if workorder_ids.filtered(lambda item: item.state in ('返工', 'rework')): + # 获取返工后新生成的工单 + work_ids = workorder_ids.filtered(lambda item: item.sequence == 0) + # 对工单进行逐个插入 + for work_id in work_ids: + for order_id in rec.workorder_ids.filtered(lambda item: item.sequence > 0): + if work_id.name == order_id.name: + work_id.sequence = order_id.sequence + 1 + break + # 对该工单之后的工单工序进行加一 + work_order_ids = rec.workorder_ids.filtered( + lambda item: item.sequence >= work_id.sequence and item.id != work_id.id) + for work in work_order_ids: + work.sequence = work.sequence + 1 + + else: + # 将工艺设计生成的工单序号赋值给工单的序号 + for work in workorder_ids: + td_ids = technology_design_ids.filtered( + lambda item: (item.route_id.name in work.name and item.process_parameters_id + and item.process_parameters_id == work.surface_technics_parameters_id) or + (item.route_id.name == work.name and item.panel + and item.panel == work.processing_panel)) + if td_ids: + work.sequence = td_ids[0].sequence + cancel_work_ids = workorder_ids.filtered(lambda item: item.state in ('已取消', 'cancel')) + if cancel_work_ids: + sequence = max(workorder_ids.filtered(lambda item: item.state not in ('已取消', 'cancel')), + key=lambda w: w.sequence).sequence + for cw in cancel_work_ids: + cw.sequence = sequence + 1 + + def _reset_work_order_sequence_1(self): + """ + 工单工序排序方法(旧) + """ for rec in self: workorder_ids = rec.workorder_ids.filtered(lambda item: item.state in ('返工', 'rework')) # 产品模型类型 @@ -767,54 +1013,32 @@ class MrpProduction(models.Model): self._reset_work_order_sequence() return True + def production_process(self, pro_plan): + type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False} + # 最后一次加工结束时间 + last_time = pro_plan.date_planned_start + # 预置时间 + works = self.workorder_ids + for index, work in enumerate(works): + count = type_map.get(work.routing_type) + date_planned_end = None + date_planned_start = None + if self.production_type == '自动化产线加工': + date_planned_start, date_planned_end, last_time = work.auto_production_process(last_time, count, + type_map) + elif self.production_type == '': + date_planned_start, date_planned_end, last_time = work.manual_offline_process(last_time, index) + work.update_work_start_end(date_planned_start, date_planned_end) + + # def def process_range_time(self): for production in self: works = production.workorder_ids pro_plan = self.env['sf.production.plan'].search([('production_id', '=', production.id)], limit=1) if not pro_plan: continue - type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False} - # 最后一次加工结束时间 - last_time = pro_plan.date_planned_start - # 预置时间 - for work in works: - count = type_map.get(work.routing_type) - date_planned_end = None - date_planned_start = None - duration_expected = datetime.timedelta(minutes=work.duration_expected) - reserve_time = datetime.timedelta(minutes=work.reserved_duration) - if not count: - # 第一轮加工 - if work.routing_type == '装夹预调': - date_planned_end = last_time - reserve_time - date_planned_start = date_planned_end - duration_expected - elif work.routing_type == 'CNC加工': - date_planned_start = last_time - date_planned_end = last_time + duration_expected - last_time = date_planned_end - else: - date_planned_start = last_time + reserve_time - date_planned_end = date_planned_start + duration_expected - last_time = date_planned_end - type_map.update({work.routing_type: True}) - else: - date_planned_start = last_time + reserve_time - date_planned_end = date_planned_start + duration_expected - last_time = date_planned_end - work.leave_id.write({ - 'date_from': date_planned_start, - 'date_to': date_planned_end, - }) - # work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end}) - # 设置一个较大的结束时间,防止在设置开始时间时,结束时间小于开始时间 - work.date_planned_finished = datetime.datetime.today() + datetime.timedelta(days=100) - work.date_planned_start = date_planned_start - work.date_planned_finished = date_planned_end - routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search( - [('name', '=', work.routing_type)]) - - work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end, - 'duration_expected': routing_workcenter.time_cycle}) + if production.production_type: + production.production_process(pro_plan) # 修改标记已完成方法 def button_mark_done1(self): @@ -838,6 +1062,7 @@ class MrpProduction(models.Model): backorders = backorders - productions_to_backorder productions_not_to_backorder._post_inventory(cancel_backorder=True) + # 查出最后一张工单完成入库操作 # if self.workorder_ids.filtered(lambda w: w.routing_type in ['表面工艺']): # move_finish = self.env['stock.move'].search([('created_production_id', '=', self.id)]) # if move_finish: @@ -938,6 +1163,14 @@ class MrpProduction(models.Model): cloud_programming = None if self.programming_state in ['已编程']: cloud_programming = self._cron_get_programming_state() + result_ids = self.detection_result_ids.filtered(lambda dr: dr.handle_result == '待处理') + work_id_list = [] + if result_ids: + work_id_list = [self.workorder_ids.filtered( + lambda wk: (wk.name == result_id.routing_type and wk.processing_panel == result_id.processing_panel + and wk.state == 'done')).id + for result_id in result_ids] + return { 'name': _('返工'), 'type': 'ir.actions.act_window', @@ -946,6 +1179,8 @@ class MrpProduction(models.Model): 'target': 'new', 'context': { 'default_production_id': self.id, + 'default_workorder_ids': self.workorder_ids.ids, + 'default_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '', 'default_reprogramming_num': cloud_programming['reprogramming_num'], 'default_programming_state': cloud_programming['programming_state'], 'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False @@ -1099,13 +1334,15 @@ class MrpProduction(models.Model): raise_user_error=not self.env.context.get('from_orderpoint')) productions = self.env['mrp.production'].sudo().search( [('origin', '=', self.origin)], order='id desc', limit=1) + productions.write({'programming_no': self.programming_no, 'is_remanufacture': True}) move = self.env['stock.move'].search([('origin', '=', productions.name)], order='id desc') for mo in move: - if mo.procure_method == 'make_to_order' and mo.name != productions.name: - if mo.name == '/': - domain = [('barcode', '=', 'WH-PC'), ('sequence_code', '=', 'PC')] - elif mo.name == '拉': - domain = [('barcode', '=', 'WH-INTERNAL'), ('sequence_code', '=', 'INT')] + domain = [] + if mo.location_id.barcode == 'WH-POSTPRODUCTION' and mo.rule_id.picking_type_id.barcode == 'PC': + domain = [('barcode', '=', 'WH-PC'), ('sequence_code', '=', 'PC')] + elif mo.location_id.barcode == 'PL' and mo.rule_id.picking_type_id.barcode == 'INT': + domain = [('barcode', '=', 'WH-INTERNAL'), ('sequence_code', '=', 'INT')] + if domain: picking_type = self.env['stock.picking.type'].search(domain) mo.write({'picking_type_id': picking_type.id}) mo._assign_picking() @@ -1123,7 +1360,6 @@ class MrpProduction(models.Model): mo_move.write({'reference': sfp_move.reference, 'partner_id': sfp_move.partner_id.id, 'picking_id': sfp_move.picking_id.id, 'picking_type_id': sfp_move.picking_type_id.id, 'production_id': False}) - productions.write({'programming_no': self.programming_no, 'is_remanufacture': True}) # productions.procurement_group_id.mrp_production_ids.move_dest_ids.write( # {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])}) stock_picking_remanufacture = self.env['stock.picking'].search([('origin', '=', productions.name)]) @@ -1157,7 +1393,6 @@ class MrpProduction(models.Model): if purchase_orders.origin.find(productions.name) == -1: purchase_orders.origin += ',' + productions.name if item['is_reprogramming'] is False: - productions._create_workorder(item) productions.programming_state = '已编程' else: productions.programming_state = '编程中' @@ -1187,6 +1422,103 @@ class MrpProduction(models.Model): 'user_id': production.user_id.id} return production_values_str + # 增加制造订单类型 + production_type = fields.Selection( + [('自动化产线加工', '自动化产线加工'), ('人工线下加工', '人工线下加工')], + string='制造类型', + compute='_compute_production_type', + store=True + ) + + @api.depends('product_id.is_manual_processing') + def _compute_production_type(self): + for production in self: + production.production_type = '自动化产线加工' if not production.product_id.is_manual_processing else '人工线下加工' + + @api.model_create_multi + def create(self, vals_list): + """ + 重载创建制造订单的方法,单个制造订单,同一成品只创建一个采购组,用于后续单据的创建 + """ + product_group_id = {} + for vals in vals_list: + if not vals.get('name', False) or vals['name'] == _('New'): + picking_type_id = vals.get('picking_type_id') + if not picking_type_id: + picking_type_id = self._get_default_picking_type_id(vals.get('company_id', self.env.company.id)) + vals['picking_type_id'] = picking_type_id + vals['name'] = self.env['stock.picking.type'].browse(picking_type_id).sequence_id.next_by_id() + if not vals.get('procurement_group_id'): + product_id = self.env['product.product'].browse(vals['product_id']) + if product_id.product_tmpl_id.single_manufacturing: + if product_id.id not in product_group_id.keys(): + procurement_group_vals = self._prepare_procurement_group_vals(vals) + group_id = self.env["procurement.group"].create(procurement_group_vals).id + vals['procurement_group_id'] = group_id + product_group_id[product_id.id] = group_id + else: + vals['procurement_group_id'] = product_group_id[product_id.id] + return super(MrpProduction, self).create(vals_list) + + @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', + 'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id') + def _compute_purchase_order_count(self): + for production in self: + # 找到来源的第一张制造订单的采购组 + if production.product_id.product_tmpl_id.single_manufacturing == True: + first_production = self.env['mrp.production'].search( + [('origin', '=', production.origin), ('product_id', '=', production.product_id.id)], limit=1, + order='id asc') + production.purchase_order_count = len( + first_production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | + first_production.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id) + else: + production.purchase_order_count = len( + production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | + production.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id) + + @api.depends('procurement_group_id', 'procurement_group_id.stock_move_ids.group_id') + def _compute_picking_ids(self): + for order in self: + if order.product_id.product_tmpl_id.single_manufacturing == True: + first_order = self.env['mrp.production'].search( + [('origin', '=', order.origin), ('product_id', '=', order.product_id.id)], limit=1, order='id asc') + order.picking_ids = self.env['stock.picking'].search([ + ('group_id', '=', first_order.procurement_group_id.id), ('group_id', '!=', False), + ]) + order.delivery_count = len(first_order.picking_ids) + else: + order.picking_ids = self.env['stock.picking'].search([ + ('group_id', '=', order.procurement_group_id.id), ('group_id', '!=', False), + ]) + order.delivery_count = len(order.picking_ids) + + def action_view_purchase_orders(self): + self.ensure_one() + if self.product_id.product_tmpl_id.single_manufacturing == True: + production = self.env['mrp.production'].search( + [('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc') + else: + production = self + purchase_order_ids = ( + production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | production.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids + action = { + 'res_model': 'purchase.order', + 'type': 'ir.actions.act_window', + } + if len(purchase_order_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': purchase_order_ids[0], + }) + else: + action.update({ + 'name': _("Purchase Order generated from %s", self.name), + 'domain': [('id', 'in', purchase_order_ids)], + 'view_mode': 'tree,form', + }) + return action + class sf_detection_result(models.Model): _name = 'sf.detection.result' @@ -1216,10 +1548,6 @@ class sf_detection_result(models.Model): 'type': 'ir.actions.act_window', 'res_id': self.id, 'views': [(self.env.ref('sf_manufacturing.sf_test_report_form').id, 'form')], - # 'view_mode': 'form', - # 'context': { - # 'default_id': self.id - # }, 'target': 'new' } diff --git a/sf_manufacturing/models/mrp_routing_workcenter.py b/sf_manufacturing/models/mrp_routing_workcenter.py index a584379f..e28bd091 100644 --- a/sf_manufacturing/models/mrp_routing_workcenter.py +++ b/sf_manufacturing/models/mrp_routing_workcenter.py @@ -7,21 +7,25 @@ class ResMrpRoutingWorkcenter(models.Model): _inherit = 'mrp.routing.workcenter' routing_type = fields.Selection([ - # ('获取CNC加工程序', '获取CNC加工程序'), ('装夹预调', '装夹预调'), - # ('前置三元定位检测', '前置三元定位检测'), ('CNC加工', 'CNC加工'), - # ('后置三元质量检测', '后置三元质量检测'), ('解除装夹', '解除装夹'), ('切割', '切割'), - ('表面工艺', '表面工艺') + ('表面工艺', '表面工艺'), + ('线切割', '线切割'), + ('人工线下加工', '人工线下加工') ], string="工序类型") + routing_tag = fields.Selection([ + ('standard', '标准'), + ('special', '特殊') + ], string="标签") is_repeat = fields.Boolean('重复', default=False) workcenter_id = fields.Many2one('mrp.workcenter', required=False) 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="表面工艺") reserved_duration = fields.Float('预留时长', default=30, tracking=True) + def get_no(self): international_standards = self.search( [('code', '!=', ''), ('active', 'in', [True, False])], @@ -78,3 +82,16 @@ class ResMrpRoutingWorkcenter(models.Model): else: workcenter_id = workcenter_ids[0] return workcenter_id + + @api.model + def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): + if self._context.get('production_id'): + route_ids = [] + technology_design = self.env['sf.technology.design'].search( + [('production_id', '=', self._context.get('production_id'))]) + for t in technology_design.filtered(lambda a: a.routing_tag == 'special'): + if not t.process_parameters_id: + route_ids.append(t.route_id.surface_technics_id.id) + domain = [('id', 'not in', route_ids), ('routing_tag', '=', 'special')] + return self._search(domain, limit=limit, access_rights_uid=name_get_uid) + return super()._name_search(name, args, operator, limit, name_get_uid) diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 37d42b42..bc012ab9 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -23,12 +23,15 @@ class ResMrpWorkOrder(models.Model): product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name') - product_tmpl_id_length = fields.Float(related='production_id.product_tmpl_id.length', readonly=True, store=True, - string="坯料长度(mm)") - product_tmpl_id_width = fields.Float(related='production_id.product_tmpl_id.width', readonly=True, store=True, - string="坯料宽度(mm)") - product_tmpl_id_height = fields.Float(related='production_id.product_tmpl_id.height', readonly=True, store=True, - string="坯料高度(mm)") + product_tmpl_id_length = fields.Float(string='坯料长度(mm)', related='material_length', readonly=True, store=False) + product_tmpl_id_width = fields.Float(string='坯料宽度(mm)', related='material_width', readonly=True, store=False) + product_tmpl_id_height = fields.Float(string='坯料高度(mm)', related='material_height', readonly=True, store=False) + # product_tmpl_id_length = fields.Float(related='production_id.product_tmpl_id.length', readonly=True, store=True, + # string="坯料长度(mm)") + # product_tmpl_id_width = fields.Float(related='production_id.product_tmpl_id.width', readonly=True, store=True, + # string="坯料宽度(mm)") + # product_tmpl_id_height = fields.Float(related='production_id.product_tmpl_id.height', readonly=True, store=True, + # string="坯料高度(mm)") product_tmpl_id_materials_id = fields.Many2one(related='production_id.product_tmpl_id.materials_id', readonly=True, store=True, check_company=True, string="材料") product_tmpl_id_materials_type_id = fields.Many2one(related='production_id.product_tmpl_id.materials_type_id', @@ -38,13 +41,10 @@ class ResMrpWorkOrder(models.Model): processing_panel = fields.Char('加工面') sequence = fields.Integer(string='工序') routing_type = fields.Selection([ - # ('获取CNC加工程序', '获取CNC加工程序'), ('装夹预调', '装夹预调'), - # ('前置三元定位检测', '前置三元定位检测'), ('CNC加工', 'CNC加工'), - # ('后置三元质量检测', '后置三元质量检测'), ('解除装夹', '解除装夹'), - ('切割', '切割'), ('表面工艺', '表面工艺') + ('切割', '切割'), ('表面工艺', '表面工艺'), ('线切割', '线切割'), ('人工线下加工', '人工线下加工') ], string="工序类型") results = fields.Char('结果') state = fields.Selection([ @@ -129,7 +129,7 @@ class ResMrpWorkOrder(models.Model): Y10_axis = fields.Float(default=0) Z10_axis = fields.Float(default=0) X_deviation_angle = fields.Integer(string="X轴偏差度", default=0) - test_results = fields.Selection([("合格", "合格"), ("返工", "返工"), ("报废", "报废")], default='合格', + test_results = fields.Selection([("合格", "合格")], default='合格', string="检测结果", tracking=True) cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工程序") cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序") @@ -137,8 +137,10 @@ class ResMrpWorkOrder(models.Model): glb_file = fields.Binary("glb模型文件", related='production_id.model_file') is_subcontract = fields.Boolean(string='是否外协') surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数") - picking_ids = fields.Many2many('stock.picking', string='外协出入库单') - # purchase_id = fields.Many2one('purchase.order', string='外协采购单') + + picking_ids = fields.Many2many('stock.picking', string='外协出入库单', compute='_compute_surface_technics_picking_ids') + + purchase_id = fields.Many2many('purchase.order', string='外协采购单') surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids') surface_technics_purchase_count = fields.Integer("外协采购", compute='_compute_surface_technics_purchase_ids') @@ -146,21 +148,107 @@ class ResMrpWorkOrder(models.Model): is_trayed = fields.Boolean(string='是否绑定托盘', default=False) tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True) + technology_design_id = fields.Many2one('sf.technology.design') + + def _compute_default_construction_period_status(self): + need_list = ['pending', 'waiting', 'ready', 'progress', 'to be detected', 'done'] + try: + if self.state not in need_list: + return False + if not self.date_planned_finished: + return False + hours = self.get_hours_diff() + if hours >= 12: + return '正常' + elif hours > 0 and hours < 12 and self.state != 'done': + return '预警' + elif hours > 0 and hours < 12 and self.state == 'done': + return '正常' + else: + return '已逾期' + except Exception as e: + logging.error("Error processing production ID {}: {}".format(self.id, e)) + raise e + + @api.depends('state', 'date_planned_finished') + def _compute_construction_period_status(self): + for worker in self: + construction_period_status = worker._compute_default_construction_period_status() + if construction_period_status and worker.construction_period_status != construction_period_status: + worker.construction_period_status = construction_period_status + + construction_period_status = fields.Selection([('正常', '正常'), ('预警', '预警'), ('已逾期', '已逾期')], + string='工期状态', + store=True, + compute='_compute_construction_period_status', + default=lambda + self: self._compute_default_construction_period_status()) + + def get_page_all_records(self, model_name, func, domain, page_size=100): + # 获取模型对象 + model = self.env[model_name].sudo() + + # 初始化分页参数 + page_number = 1 + while True: + # 计算偏移量 + offset = (page_number - 1) * page_size + + # 获取当前页的数据 + records = model.search(domain, limit=page_size, offset=offset) + + # 如果没有更多记录,退出循环 + if not records: + break + + # 将当前页的数据添加到结果列表 + func(records) + # 增加页码 + page_number += 1 + + def run_compute_construction_period_status(self, records): + records._compute_construction_period_status() + + def _corn_update_construction_period_status(self): + need_list = ['pending', 'waiting', 'ready', 'progress', 'to be detected'] + # need_list = [ + # 'progress', + # 'to be detected'] + self.get_page_all_records('mrp.workorder', self.run_compute_construction_period_status, + [('state', 'in', need_list)], 100) + + def get_hours_diff(self): + # 获取当前日期和时间 + current_datetime = fields.Datetime.now() + + # 将 date_field 转换为 datetime 对象 + if self.date_planned_finished: + date_obj = fields.Datetime.from_string(self.date_planned_finished) + # 将 date 对象转换为 datetime 对象,设置时间为 00:00:00 + # date_obj = datetime.datetime.combine(date_obj, datetime.time.min) + + # 计算两个日期之间的差值 + delta = date_obj - current_datetime + + # 返回差值的小时数 + return int(delta.total_seconds() / 3600) + else: + return 0.0 @api.depends('name', 'production_id.name') def _compute_surface_technics_picking_ids(self): for workorder in self: if workorder.routing_type == '表面工艺': - domain = [('origin', '=', workorder.production_id.name)] + domain = [('origin', '=', workorder.production_id.name), ('state', 'not in', ['cancel']), + ('partner_id', '=', workorder.supplier_id.id)] previous_workorder = self.env['mrp.workorder'].search( [('sequence', '=', workorder.sequence - 1), ('routing_type', '=', '表面工艺'), ('production_id', '=', workorder.production_id.id)]) - if previous_workorder: - process_product = self.env['product.template']._get_process_parameters_product( - previous_workorder.surface_technics_parameters_id) - domain += [('partner_id', '=', process_product.partner_id.id)] - else: - domain += [('surface_technics_parameters_id', '=', workorder.surface_technics_parameters_id.id)] + # if previous_workorder: + # if previous_workorder.supplier_id != workorder.supplier_id: + # domain += [('surface_technics_parameters_id', '=', workorder.surface_technics_parameters_id.id)] + # else: + domain += [('surface_technics_parameters_id', '=', workorder.surface_technics_parameters_id.id)] picking_ids = self.env['stock.picking'].search(domain, order='id asc') workorder.surface_technics_picking_count = len(picking_ids) workorder.picking_ids = picking_ids.ids @@ -184,34 +272,72 @@ class ResMrpWorkOrder(models.Model): @api.depends('state', 'production_id.name') def _compute_surface_technics_purchase_ids(self): for order in self: - if order.routing_type == '表面工艺': - production_programming = self.env['mrp.production'].search( - [('programming_no', '=', order.production_id.programming_no)], order='name asc') - production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False) - production_list = [production.name for production in production_programming] - purchase = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))]) - for line in purchase.order_line: - if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id and line.product_qty == len( - production_no_remanufacture): - order.surface_technics_purchase_count = len(purchase) + if order.routing_type == '表面工艺' and order.state not in ['cancel']: + # if order.production_id.production_type == '自动化产线加工': + # domain = [('programming_no', '=', order.production_id.programming_no)] + # else:buzhdiao + # domain = [('origin', '=', order.production_id.origin)] + # production_programming = self.env['mrp.production'].search(domain, order='name asc') + # production_list = [production.name for production in production_programming] + # production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False) + # technology_design = self.env['sf.technology.design'].search( + # [('process_parameters_id', '=', order.surface_technics_parameters_id.id), + # ('production_id', '=', order.production_id.id)]) + # if technology_design.is_auto is False: + # domain = [('origin', '=', order.production_id.name)] + # else: + domain = [('purchase_type', '=', 'consignment'), ('origin', '=', order.production_id.name), + ('state', '!=', 'cancel')] + purchase = self.env['purchase.order'].search(domain) + purchase_num = 0 + if not purchase: + order.surface_technics_purchase_count = 0 + for po in purchase: + for line in po.order_line: + if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id: + if line.product_qty == 1: + purchase_num += 1 + order.surface_technics_purchase_count = purchase_num else: order.surface_technics_purchase_count = 0 def action_view_surface_technics_purchase(self): self.ensure_one() - production_programming = self.env['mrp.production'].search( - [('programming_no', '=', self.production_id.programming_no)], order='name asc') - production_list = [production.name for production in production_programming] - purchase_orders = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))]) + # if self.routing_type == '表面工艺': + # if self.production_id.production_type == '自动化产线加工': + # domain = [('programming_no', '=', self.production_id.programming_no)] + # else: + # domain = [('origin', '=', self.production_id.origin)] + # production_programming = self.env['mrp.production'].search(domain, order='name asc') + # production_list = [production.name for production in production_programming] + # production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False) + # technology_design = self.env['sf.technology.design'].search( + # [('process_parameters_id', '=', self.surface_technics_parameters_id.id), + # ('production_id', '=', self.production_id.id)]) + # if technology_design.is_auto is False: + # domain = [('origin', '=', self.production_id.name)] + # else: + purchase_orders_id = self._get_surface_technics_purchase_ids() result = { "type": "ir.actions.act_window", "res_model": "purchase.order", - "res_id": purchase_orders.id, + "res_id": purchase_orders_id.id, # "domain": [['id', 'in', self.purchase_id]], "name": _("Purchase Orders"), 'view_mode': 'form', } return result + + def _get_surface_technics_purchase_ids(self): + domain = [('origin', '=', self.production_id.name), ('purchase_type', '=', 'consignment')] + purchase_orders = self.env['purchase.order'].search(domain) + 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 + return purchase_orders_id supplier_id = fields.Many2one('res.partner', string='外协供应商') equipment_id = fields.Many2one('maintenance.equipment', string='加工设备', tracking=True) @@ -228,6 +354,7 @@ class ResMrpWorkOrder(models.Model): part_number = fields.Char(related='production_id.part_number', string='零件图号') machining_drawings = fields.Binary('2D加工图纸', related='production_id.part_drawing', readonly=True) quality_standard = fields.Binary('质检标准', related='production_id.quality_standard', readonly=True) + part_name = fields.Char(related='production_id.part_name', string='零件名称') # 工序状态 process_state = fields.Selection([ @@ -279,8 +406,11 @@ class ResMrpWorkOrder(models.Model): 保存名称 """ for record in self: - tem_name = record.production_id.name.replace('/', '_') - record.save_name = tem_name + '_' + record.processing_panel + if record.processing_panel: + tem_name = record.production_id.name.replace('/', '_') + record.save_name = tem_name + '_' + record.processing_panel + else: + record.save_name = '' schedule_state = fields.Selection(related='production_id.schedule_state', store=True) # 工件装夹信息 @@ -327,7 +457,8 @@ class ResMrpWorkOrder(models.Model): @api.constrains('blocked_by_workorder_ids') def _check_no_cyclic_dependencies(self): - if self.production_id.state not in ['rework'] and self.state not in ['rework']: + if self.production_id.state not in ['rework', 'technology_to_confirmed', 'confirmed'] and self.state not in [ + 'rework']: if not self._check_m2m_recursion('blocked_by_workorder_ids'): raise ValidationError(_("您不能创建周期性的依赖关系.")) @@ -338,8 +469,9 @@ class ResMrpWorkOrder(models.Model): for workorder in self.blocked_by_workorder_ids: if workorder.state in ['done', 'cancel', 'rework']: continue - workorder._plan_workorder(replan) - start_date = max(start_date, workorder.date_planned_finished) + if workorder.production_id.state not in ['technology_to_confirmed', 'confirmed']: + workorder._plan_workorder(replan) + start_date = max(start_date, workorder.date_planned_finished) # Plan only suitable workorders if self.state not in ['pending', 'waiting', 'ready']: return @@ -669,35 +801,14 @@ class ResMrpWorkOrder(models.Model): }} # 拼接工单对象属性值 - def json_workorder_str(self, k, production, route, item): + def json_workorder_str(self, production, route): # 计算预计时长duration_expected routing_types = ['切割', '装夹预调', 'CNC加工', '解除装夹'] - if route.routing_type in routing_types: + if route.route_id.routing_type in routing_types: routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search( - [('name', '=', route.routing_type)]) + [('name', '=', route.routing_type if hasattr(route, 'routing_type') else route.route_id.routing_type)]) duration_expected = routing_workcenter.time_cycle reserved_duration = routing_workcenter.reserved_duration - # if route.routing_type == '切割': - # duration_expected = self.env['mrp.routing.workcenter'].sudo().search( - # [('name', '=', '切割')]).time_cycle - # # elif route.routing_type == '获取CNC加工程序': - # # duration_expected = self.env['mrp.routing.workcenter'].sudo().search( - # # [('name', '=', '获取CNC加工程序')]).time_cycle - # elif route.routing_type == '装夹预调': - # duration_expected = self.env['mrp.routing.workcenter'].sudo().search( - # [('name', '=', '装夹预调')]).time_cycle - # # elif route.routing_type == '前置三元定位检测': - # # duration_expected = self.env['mrp.routing.workcenter'].sudo().search( - # # [('name', '=', '前置三元定位检测')]).time_cycle - # elif route.routing_type == 'CNC加工': - # duration_expected = self.env['mrp.routing.workcenter'].sudo().search( - # [('name', '=', 'CNC加工')]).time_cycle - # # elif route.routing_type == '后置三元质量检测': - # # duration_expected = self.env['mrp.routing.workcenter'].sudo().search( - # # [('name', '=', '后置三元质量检测')]).time_cycle - # elif route.routing_type == '解除装夹': - # duration_expected = self.env['mrp.routing.workcenter'].sudo().search( - # [('name', '=', '解除装夹')]).time_cycle else: duration_expected = 60 reserved_duration = 30 @@ -705,26 +816,22 @@ class ResMrpWorkOrder(models.Model): 'product_uom_id': production.product_uom_id.id, 'qty_producing': 0, 'operation_id': False, - 'name': route.route_workcenter_id.name, - 'processing_panel': k, - 'quality_point_ids': route.route_workcenter_id.quality_point_ids, - 'routing_type': route.routing_type, - # 'work_state': '待发起', - 'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids, - route.routing_type, - production.product_id), + 'name': route.name if hasattr(route, 'routing_type') else route.route_id.name, + 'processing_panel': False if hasattr(route, 'routing_type') else route.panel, + 'sequence': route.sequence, + 'quality_point_ids': False if hasattr(route, 'routing_type') else route.route_id.quality_point_ids, + 'routing_type': route.routing_type if hasattr(route, 'routing_type') else route.route_id.routing_type, + 'workcenter_id': False if hasattr(route, 'routing_type') else self.env[ + 'mrp.routing.workcenter'].get_workcenter(route.route_id.workcenter_ids.ids, + route.route_id.routing_type, + production.product_id), # 设定初始化值,避免出现变成bool问题 'date_planned_start': datetime.now(), 'date_planned_finished': datetime.now() + timedelta(days=1), 'duration_expected': duration_expected, 'duration': 0, - 'tag_type': '重新加工' if item is False else False, - 'cnc_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cnc.processing']._json_cnc_processing( - k, item), - 'cmm_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cmm.program']._json_cmm_program(k, - item), - # 'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list( - # production) + 'technology_design_id': route.id, + # 'tag_type': '重新加工' if item is False else False, 'reserved_duration': reserved_duration, }] return workorders_values_str @@ -755,22 +862,24 @@ class ResMrpWorkOrder(models.Model): ] # 拼接工单对象属性值(表面工艺) - def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id): + def _json_workorder_surface_process_str(self, production, route, supplier_id): workorders_values_str = [0, '', { 'product_uom_id': production.product_uom_id.id, 'qty_producing': 0, 'operation_id': False, - 'name': '%s-%s' % (route.name, process_parameter.name), + 'name': route.process_parameters_id.display_name, 'processing_panel': '', + 'sequence': route.sequence, + 'technology_design_id': route.id, 'routing_type': '表面工艺', - 'surface_technics_parameters_id': process_parameter.id, + 'surface_technics_parameters_id': route.process_parameters_id.id, 'work_state': '', 'supplier_id': supplier_id, - 'is_subcontract': True if process_parameter.gain_way == '外协' else False, + 'is_subcontract': True if route.process_parameters_id.gain_way == '外协' else False, 'workcenter_id': self.env[ - 'mrp.workcenter'].get_process_outsourcing_workcenter() if process_parameter.gain_way == '外协' else - self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids, - route.routing_type, + 'mrp.workcenter'].get_process_outsourcing_workcenter() if route.process_parameters_id.gain_way == '外协' else + self.env['mrp.routing.workcenter'].get_workcenter(route.route_id.workcenter_ids.ids, + route.route_id.routing_type, production.product_id), 'date_planned_start': datetime.now(), 'date_planned_finished': datetime.now() + timedelta(days=1), @@ -922,208 +1031,223 @@ class ResMrpWorkOrder(models.Model): return workorders_values_str @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state', - 'production_id.tool_state') + 'production_id.tool_state', 'production_id.schedule_state', 'sequence', + 'production_id.programming_state') def _compute_state(self): - super()._compute_state() - for workorder in self: - re_work = self.env['mrp.workorder'].search([('production_id', '=', workorder.production_id.id), - ('processing_panel', '=', workorder.processing_panel), - ('is_rework', '=', True), ('state', 'in', ['done', 'rework'])]) - cnc_workorder = self.env['mrp.workorder'].search( - [('production_id', '=', workorder.production_id.id), - ('processing_panel', '=', workorder.processing_panel), - ('routing_type', '=', 'CNC加工'), ('state', 'in', ['done', 'rework']), - ('test_results', '=', '返工')]) - cnc_workorder_pending = self.env['mrp.workorder'].search( - [('production_id', '=', workorder.production_id.id), - ('processing_panel', '=', workorder.processing_panel), - ('routing_type', '=', 'CNC加工'), ('state', 'in', ['pending'])]) - unclamp_workorder = self.env['mrp.workorder'].search( - [('production_id', '=', workorder.production_id.id), - ('sequence', '=', workorder.sequence - 1), - ('state', 'in', ['done'])]) - if workorder.state not in ['cancel', 'progress', 'rework']: - if workorder.production_id.state == 'rework': - if workorder.routing_type == '装夹预调' and workorder.state not in ['done', 'rework', - 'cancel']: - # # 有返工工单 - # if re_work: - # 新工单 - if workorder.is_rework is False: - if workorder.production_id.programming_state == '已编程' and workorder.production_id.is_rework is False: - if re_work or cnc_workorder: - workorder.state = 'ready' - else: - if workorder.production_id.is_rework is True: - if re_work or cnc_workorder: - workorder.state = 'waiting' - - elif workorder.routing_type == 'CNC加工' and workorder.state not in ['done', 'rework', 'cancel']: - pre_workorder = self.env['mrp.workorder'].search( - [('production_id', '=', workorder.production_id.id), - ('processing_panel', '=', workorder.processing_panel), - ('routing_type', '=', '装夹预调'), ('state', '=', 'done')]) - if pre_workorder: - if re_work: - workorder.state = 'waiting' - elif workorder.routing_type == '解除装夹' and workorder.state not in ['done', 'rework', 'cancel']: - if cnc_workorder: - if not cnc_workorder_pending or unclamp_workorder.test_results == '报废': - workorder.state = 'waiting' - # else: - # if workorder.production_id.is_rework is True: - # workorder.state = 'waiting' - elif workorder.production_id.state == 'progress': - if workorder.routing_type == '装夹预调' and workorder.production_id.programming_state == '已编程' and \ - workorder.is_rework is False and workorder.state not in [ - 'done', 'rework', - 'cancel']: - if workorder.production_id.is_rework is False: - if re_work or cnc_workorder or unclamp_workorder: - workorder.state = 'ready' - # if (re_work or cnc_workorder) and workorder.production_id.is_rework is False: - # workorder.state = 'ready' - if workorder.routing_type == '表面工艺' and workorder.state not in ['done', 'progress']: - if unclamp_workorder: - if workorder.is_subcontract is False: - workorder.state = 'ready' - else: - production_programming = self.env['mrp.production'].search( - [('programming_no', '=', self.production_id.programming_no)], order='name asc') - production_no_remanufacture = production_programming.filtered( - lambda a: a.is_remanufacture is False) - production_list = [production.name for production in production_programming] - purchase_orders = self.env['purchase.order'].search( - [('origin', 'ilike', ','.join(production_list))]) - for line in purchase_orders.order_line: - if line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id and line.product_qty == len( - production_no_remanufacture): - if purchase_orders.state == 'purchase': - workorder.state = 'ready' - else: - workorder.state = 'waiting' - elif workorder.production_id.state == 'scrap': - if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废': - workorder.state = 'waiting' - if workorder.routing_type == '装夹预调' and workorder.state in ['waiting', 'ready', 'pending']: - workorder_ids = workorder.production_id.workorder_ids - work_bo = True - for wo in workorder_ids.filtered(lambda a: a.routing_type == '装夹预调' and a.state == 'rework'): - if not workorder_ids.filtered( - lambda a: (a.routing_type == '装夹预调' and a.state not in ['rework', 'cancel'] - and a.processing_panel == wo.processing_panel)): - work_bo = False - break - if (workorder.production_id.programming_state == '已编程' and work_bo - and not workorder_ids.filtered(lambda a: a.sequence == 0)): - # 当工单对应制造订单的功能刀具状态为 【无效刀】时,先对的第一个装夹预调工单状态设置为 【等待组件】 - if workorder.production_id.tool_state in ['1', '2']: - if workorder.state in ['ready']: - workorder.state = 'waiting' - continue - elif workorder.state in ['waiting']: - continue - elif workorder.state == 'pending' and workorder == self.search( - [('production_id', '=', workorder.production_id.id), - ('routing_type', '=', '装夹预调'), - ('state', 'not in', ['rework', 'done', 'cancel'])], - limit=1, - order="sequence"): - workorder.state = 'waiting' - continue - elif workorder.production_id.tool_state in ['0']: - if workorder_ids.filtered(lambda a: a.state == 'rework'): - if not workorder_ids.filtered( - lambda a: (a.routing_type not in ['装夹预调'] and - a.state not in ['pending', 'done', 'rework', 'cancel'])): - # 查询工序最小的非完工、非返工的装夹预调工单 - work_id = self.search( - [('production_id', '=', workorder.production_id.id), - ('state', 'not in', ['rework', 'done', 'cancel'])], - limit=1, - order="sequence") - if work_id.routing_type == '装夹预调': - if workorder == work_id: - if workorder.production_id.reservation_state == 'assigned': - workorder.state = 'ready' - elif workorder.production_id.reservation_state != 'assigned': - workorder.state = 'waiting' - continue - elif (workorder.name == '装夹预调' and - workorder.state not in ['rework', 'done', 'cancel']): - if workorder.state != 'pending': - workorder.state = 'pending' - if workorder.production_id.tool_state in ['1', '2'] and workorder.state == 'ready': - workorder.state = 'waiting' - continue - if (workorder.production_id.tool_state in ['1', '2'] - and not workorder.production_id.workorder_ids.filtered(lambda a: a.sequence == 0) - and workorder.production_id.programming_state == '编程中' and workorder.name == '装夹预调'): - if workorder.state == 'pending' and workorder == self.search( - [('production_id', '=', workorder.production_id.id), - ('routing_type', '=', '装夹预调'), - ('state', 'not in', ['rework', 'done', 'cancel'])], - limit=1, - order="sequence"): - workorder.state = 'waiting' - continue - - # elif workorder.routing_type == 'CNC加工' and workorder.state not in ['done', 'cancel', 'progress', - # 'rework']: - # per_work = self.env['mrp.workorder'].search( - # [('routing_type', '=', '装夹预调'), ('production_id', '=', workorder.production_id.id), - # ('processing_panel', '=', workorder.processing_panel), ('is_rework', '=', True)]) - # if per_work: - # workorder.state = 'waiting' - # if workorder.routing_type == 'CNC加工' and workorder.state == 'progress': - # workorder.state = 'to be detected' - + # super()._compute_state() # for workorder in self: - # if workorder.is_rework is True and workorder.state == 'done': - # cnc_work = self.env['mrp.workorder'].search([('routing_type','=','CNC加工'),('production_id','=',workorder.production_id.id)]) - # if cnc_work: - # cnc_work.state = 'waiting' + # if workorder.sequence != 1: + # previous_workorder = self.env['mrp.workorder'].search( + # [('production_id', '=', workorder.production_id.id), + # ('sequence', '=', workorder.sequence - 1)]) # if workorder.state == 'pending': # if all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]): - # workorder.state = 'ready' if workorder.production_id.reservation_state == 'assigned' else 'waiting' - # continue + # if workorder.production_id.reservation_state == 'assigned' and workorder.production_id.schedule_state == '已排': + # if ((workorder.sequence == 1 and not workorder.blocked_by_workorder_ids) + # or (workorder.blocked_by_workorder_ids.state in ('done', 'cancel') + # and workorder.blocked_by_workorder_ids.test_results not in ['报废', '返工']) + # or (previous_workorder.state in ('done', 'cancel') + # and not workorder.blocked_by_workorder_ids + # and previous_workorder.test_results not in ['报废', '返工']) + # ): + # workorder.state = 'ready' + # continue + # if workorder.production_id.schedule_state == '未排' and workorder.state in ('waiting', 'ready'): + # if workorder.sequence != 1: + # workorder.state = 'pending' + # continue # if workorder.state not in ('waiting', 'ready'): # continue + # if workorder.state in ( + # 'waiting') and workorder.sequence == 1 and workorder.production_id.schedule_state == '已排': + # workorder.state = 'ready' + # continue # if not all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]): # workorder.state = 'pending' + # if workorder.state in ['waiting']: + # if previous_workorder.state == 'waiting': + # workorder.state = 'pending' + # if workorder.sequence == 1 and workorder.state == 'pending': + # workorder.state = 'waiting' # continue # if workorder.production_id.reservation_state not in ('waiting', 'confirmed', 'assigned'): # continue - # if workorder.production_id.reservation_state == 'assigned' and workorder.state == 'waiting': + # if workorder.production_id.reservation_state == 'assigned' and workorder.state == 'waiting' and workorder.production_id.schedule_state == '已排': # workorder.state = 'ready' # elif workorder.production_id.reservation_state != 'assigned' and workorder.state == 'ready': # workorder.state = 'waiting' + for workorder in self: + # 如果工单的工序没有进行排序则跳出循环 + if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0): + continue + # ===== 对所有按序号排序的非[进行中、完成、返工、取消]状态的工单,除了第一条之外的工单状态都设置为[等待其他工单] ===== + # logging.info(workorder.state) + work_ids = workorder.production_id.workorder_ids.filtered( + lambda wk: wk.state not in ['done', 'rework', 'cancel']) + if not work_ids: + continue + min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence) + if workorder.state in ['done', 'rework', 'cancel', 'progress', 'to be detected']: + continue + else: + if workorder != min_sequence_wk: + if workorder.state != 'pending': + workorder.state = 'pending' + continue + # ================= 如果制造订单制造类型为【人工线下加工】========================== + if (workorder.production_id.production_type == '人工线下加工' + and workorder.production_id.schedule_state == '已排' + and len(workorder.production_id.picking_ids.filtered( + lambda w: w.state not in ['done', 'cancel'])) == 0): + if workorder.is_subcontract is True: + purchase_orders_id = self._get_surface_technics_purchase_ids() + if purchase_orders_id.state == 'purchase': + workorder.state = 'ready' + continue + else: + workorder.state = 'waiting' + continue + else: + workorder.state = 'ready' + continue + # ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]========================== + if (workorder.production_id.tool_state in ['1', '2'] or workorder.production_id.state == 'rework' + or workorder.production_id.schedule_state != '已排' + or workorder.production_id.reservation_state not in ['assigned'] + or workorder.production_id.workorder_ids.filtered( + lambda wk: wk.sequence == workorder.sequence - 1).test_results in ['报废', '返工']): + if workorder.state != 'waiting': + workorder.state = 'waiting' + continue + if workorder.production_id.programming_state == '已编程': + workorder.state = 'ready' + elif workorder.state != 'waiting': + workorder.state = 'waiting' + # =========== 特殊工艺工单处理 =================== + # if workorder.routing_type == '表面工艺' and workorder.is_subcontrac: + # purchase_order = self.env['purchase.order'].search( + # [('origin', 'ilike', workorder.production_id.name)]) + # if purchase_order.picking_ids.filtered(lambda p: p.state in ['waiting', 'confirmed', 'assigned']): + # workorder.state = 'waiting' + # continue + if workorder.technology_design_id.routing_tag == 'special': + if workorder.is_subcontract is False: + workorder.state = 'ready' + else: + # production_programming = self.env['mrp.production'].search( + # [('origin', '=', self.production_id.origin)], order='name asc') + # production_no_remanufacture = production_programming.filtered( + # lambda a: a.is_remanufacture is False) + # production_list = [production.name for production in production_programming] + # purchase_orders = self.env['purchase.order'].search( + # [('origin', 'ilike', ','.join(production_list))]) + # for line in purchase_orders.order_line: + # if ( + # line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id + # and line.product_qty == len(production_no_remanufacture)): + # if all(pur_order.state == 'purchase' for pur_order in purchase_orders): + # workorder.state = 'ready' + # else: + # workorder.state = 'waiting' + purchase_orders_id = self._get_surface_technics_purchase_ids() + if purchase_orders_id: + workorder.state = 'ready' if purchase_orders_id.state == 'purchase' else 'waiting' + else: + workorder.state = 'waiting' + + # re_work = self.env['mrp.workorder'].search([('production_id', '=', workorder.production_id.id), + # ('processing_panel', '=', workorder.processing_panel), + # ('is_rework', '=', True), ('state', 'in', ['done', 'rework'])]) + # cnc_workorder = self.env['mrp.workorder'].search( + # [('production_id', '=', workorder.production_id.id), + # ('processing_panel', '=', workorder.processing_panel), + # ('routing_type', '=', 'CNC加工'), ('state', 'in', ['done', 'rework']), + # ('test_results', '=', '返工')]) + # cnc_workorder_pending = self.env['mrp.workorder'].search( + # [('production_id', '=', workorder.production_id.id), + # ('processing_panel', '=', workorder.processing_panel), + # ('routing_type', '=', 'CNC加工'), ('state', 'in', ['pending'])]) + # unclamp_workorder = self.env['mrp.workorder'].search( + # [('production_id', '=', workorder.production_id.id), + # ('sequence', '=', workorder.sequence - 1), + # ('state', 'in', ['done'])]) + # if workorder.state not in ['cancel', 'progress', 'rework']: + # if workorder.production_id.state == 'rework': + # if workorder.routing_type == '装夹预调': + # # # 有返工工单 + # # if re_work: + # # 新工单 + # if workorder.is_rework is False: + # if (workorder.production_id.programming_state == '已编程' + # and workorder.production_id.is_rework is False): + # if re_work or cnc_workorder: + # workorder.state = 'ready' + # else: + # if workorder.production_id.is_rework is True: + # if re_work or cnc_workorder: + # workorder.state = 'waiting' + # + # elif workorder.routing_type == 'CNC加工': + # pre_workorder = self.env['mrp.workorder'].search( + # [('production_id', '=', workorder.production_id.id), + # ('processing_panel', '=', workorder.processing_panel), + # ('routing_type', '=', '装夹预调'), ('state', '=', 'done')]) + # if pre_workorder: + # if re_work: + # workorder.state = 'waiting' + # elif workorder.routing_type == '解除装夹': + # if cnc_workorder: + # if not cnc_workorder_pending or unclamp_workorder.test_results == '报废': + # workorder.state = 'waiting' + # # else: + # # if workorder.production_id.is_rework is True: + # # workorder.state = 'waiting' + # elif workorder.production_id.state == 'progress': + # if (workorder.routing_type == '装夹预调' and workorder.production_id.programming_state == '已编程' + # and workorder.is_rework is False and workorder.state not in ['done', 'rework', 'cancel']): + # if workorder.production_id.is_rework is False: + # if re_work or cnc_workorder or unclamp_workorder: + # workorder.state = 'ready' + # # if (re_work or cnc_workorder) and workorder.production_id.is_rework is False: + # # workorder.state = 'ready' + # elif workorder.production_id.state == 'scrap': + # if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废': + # workorder.state = 'waiting' + # 重写工单开始按钮方法 def button_start(self): + # 判断工单状态是否为等待组件 + if self.state in ['waiting', 'pending']: + raise UserError('制造订单【%s】缺少组件信息!' % self.production_id.name) + if self.routing_type == 'CNC加工': self.env['sf.production.plan'].sudo().search([('name', '=', self.production_id.name)]).write({ 'state': 'processing', 'actual_start_time': datetime.now() }) - if self.routing_type == '装夹预调': + if self.sequence == 1: # 判断是否有坯料的序列号信息 boolean = False if self.production_id.move_raw_ids: - if self.production_id.move_raw_ids[0].move_line_ids: + if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料': if self.production_id.move_raw_ids[0].move_line_ids: - if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name: - boolean = True + if self.production_id.move_raw_ids[0].move_line_ids: + if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name: + boolean = True + else: + boolean = True if not boolean: raise UserError('制造订单【%s】缺少组件的序列号信息!' % self.production_id.name) self.pro_code = self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name # cnc校验 - cnc_workorder = self.search( - [('production_id', '=', self.production_id.id), ('routing_type', '=', 'CNC加工')], - limit=1, order='id asc') - if not cnc_workorder.cnc_ids: - raise UserError(_('该制造订单还未下发CNC程序,请稍后再试')) + if self.production_id.production_type == '自动化产线加工': + cnc_workorder = self.search( + [('production_id', '=', self.production_id.id), ('routing_type', '=', 'CNC加工')], + limit=1, order='id asc') + # if not cnc_workorder.cnc_ids: + # raise UserError(_('该制造订单还未下发CNC程序,请稍后再试')) else: if self.production_id.tool_state in ['1', '2']: if self.production_id.tool_state == '1': @@ -1140,15 +1264,33 @@ class ResMrpWorkOrder(models.Model): # 表面工艺外协出库单 if self.routing_type == '表面工艺': if self.is_subcontract is True: - move_out = self.env['stock.move'].search( - [('location_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), - ('location_dest_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'VL-SPOC')]).id), - ('origin', '=', self.production_id.name)]) - if move_out.state != 'done': - move_out.write({'state': 'assigned', 'production_id': False}) - self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self)) + move_out = self.move_subcontract_workorder_ids[1] + # move_out = self.env['stock.move'].search( + # [('location_id', '=', self.env['stock.location'].search( + # [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), + # ('location_dest_id', '=', self.env['stock.location'].search( + # [('barcode', 'ilike', 'VL-SPOC')]).id), + # ('origin', '=', self.production_id.name), ('state', 'not in', ['cancel', 'done'])]) + for mo in move_out: + if mo.state != 'done': + mo.write({'state': 'assigned', 'production_id': False}) + if not mo.move_line_ids: + self.env['stock.move.line'].create(mo.get_move_line(self.production_id, self)) + # product_qty = mo.product_uom._compute_quantity( + # mo.product_uom_qty, mo.product_id.uom_id, rounding_method='HALF-UP') + # available_quantity = self.env['stock.quant']._get_available_quantity( + # mo.product_id, + # mo.location_id, + # lot_id=mo.move_line_ids.lot_id, + # strict=False, + # ) + # mo._update_reserved_quantity( + # product_qty, + # available_quantity, + # mo.location_id, + # lot_id=mo.move_line_ids.lot_id, + # strict=False, + # ) # move_out._action_assign() if self.state == 'waiting' or self.state == 'ready' or self.state == 'progress': @@ -1218,8 +1360,8 @@ class ResMrpWorkOrder(models.Model): if record.is_rework is False: if not record.material_center_point: raise UserError("坯料中心点为空,请检查") - # if record.X_deviation_angle <= 0: - # raise UserError("X偏差角度小于等于0,请检查!本次计算的X偏差角度为:%s" % record.X_deviation_angle) + # if record.X_deviation_angle <= 0: + # raise UserError("X偏差角度小于等于0,请检查!本次计算的X偏差角度为:%s" % record.X_deviation_angle) record.process_state = '待加工' # record.write({'process_state': '待加工'}) record.production_id.process_state = '待加工' @@ -1249,23 +1391,17 @@ class ResMrpWorkOrder(models.Model): ''' record.date_finished = datetime.now() if record.routing_type == '表面工艺': - logging.info('record.picking_ids:%s' % record.picking_ids) - logging.info('record.picking_out:%s' % record.picking_ids[0]) if record.picking_ids: - for pick_item in record.picking_ids: - if pick_item.state not in ['done']: - raise UserError( - '请先完成该工单的工艺外协再进行操作') - picking_out = record.env['stock.move.line'].search( - [('picking_id', '=', record.picking_ids[0].id)]) - logging.info('picking_out:%s' % picking_out.picking_id.name) - # if picking_out: - # order_line_ids = [] - # logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name) - # - # else: - # raise UserError( - # '请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name) + picks = record.picking_ids.filtered(lambda p: p.state not in ('done')) + if picks: + raise UserError('请先完成该工单的工艺外协再进行操作') + # 表面工艺外协,最后一张工单 + workorders = self.production_id.workorder_ids + subcontract_workorders = workorders.filtered(lambda wo: wo.is_subcontract == True).sorted('sequence') + if self == subcontract_workorders[-1]: + # 给下一个库存移动就绪 + self.move_subcontract_workorder_ids[0].move_dest_ids._action_done() + # self.production_id.button_mark_done() tem_date_planned_finished = record.date_planned_finished tem_date_finished = record.date_finished logging.info('routing_type:%s' % record.routing_type) @@ -1284,7 +1420,7 @@ class ResMrpWorkOrder(models.Model): # record.recreateManufacturingOrWorkerOrder() is_production_id = False rework_workorder = record.production_id.workorder_ids.filtered(lambda p: p.state == 'rework') - done_workorder = record.production_id.workorder_ids.filtered(lambda p1: p1.state == 'done') + done_workorder = record.production_id.workorder_ids.filtered(lambda p1: p1.state in ['done']) if (len(rework_workorder) + len(done_workorder) == len(record.production_id.workorder_ids)) or ( len(done_workorder) == len(record.production_id.workorder_ids)): is_production_id = True @@ -1301,19 +1437,20 @@ 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 and record.routing_type in ['解除装夹', '表面工艺']: + # if is_production_id is True and record.routing_type in ['解除装夹', '表面工艺', '切割']: + 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: move_raw_id.quantity_done = move_raw_id.product_uom_qty record.process_state = '已完工' record.production_id.process_state = '已完工' - if record.routing_type in ['表面工艺']: - raw_move = self.env['stock.move'].sudo().search( - [('origin', '=', record.production_id.name), - ('procure_method', 'in', ['make_to_order', 'make_to_stock']), - ('state', '!=', 'done')]) - if raw_move: - raw_move.write({'state': 'done'}) + # if record.routing_type in ['表面工艺']: + # raw_move = self.env['stock.move'].sudo().search( + # [('origin', '=', record.production_id.name), + # ('procure_method', 'in', ['make_to_order', 'make_to_stock']), + # ('state', '!=', 'done')]) + # if raw_move: + # raw_move.write({'state': 'done'}) record.production_id.button_mark_done1() # record.production_id.state = 'done' @@ -1433,6 +1570,8 @@ class ResMrpWorkOrder(models.Model): 'default_confirm_button': '确认解除', # 'default_feeder_station_start_id': feeder_station_start_id, }} + + move_subcontract_workorder_ids = fields.One2many('stock.move', 'subcontract_workorder_id', string='组件') class CNCprocessing(models.Model): @@ -1956,3 +2095,56 @@ class CMMprogram(models.Model): 'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'), })) return cmm_program + + def update_work_start_end(self, date_planned_start, date_planned_end): + self.leave_id.write({ + 'date_from': date_planned_start, + 'date_to': date_planned_end, + }) + self.date_planned_finished = datetime.datetime.today() + datetime.timedelta(days=100) + self.date_planned_start = date_planned_start + self.date_planned_finished = date_planned_end + routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search( + [('name', '=', self.routing_type)]) + + self.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end, + 'duration_expected': routing_workcenter.time_cycle}) + + def auto_production_process(self, last_time, is_first, type_map): + date_planned_end = None + date_planned_start = None + duration_expected = datetime.timedelta(minutes=self.duration_expected) + reserve_time = datetime.timedelta(minutes=self.reserved_duration) + if is_first: + # 第一轮加工 + if self.routing_type == '装夹预调': + date_planned_end = last_time - reserve_time + date_planned_start = date_planned_end - duration_expected + elif self.routing_type == 'CNC加工': + date_planned_start = last_time + date_planned_end = last_time + duration_expected + last_time = date_planned_end + else: + date_planned_start = last_time + reserve_time + date_planned_end = date_planned_start + duration_expected + last_time = date_planned_end + type_map.update({self.routing_type: True}) + else: + date_planned_start = last_time + reserve_time + date_planned_end = date_planned_start + duration_expected + last_time = date_planned_end + return date_planned_start, date_planned_end, last_time + + def manual_offline_process(self, last_time, is_first): + date_planned_end = None + date_planned_start = None + duration_expected = datetime.timedelta(minutes=self.duration_expected) + reserve_time = datetime.timedelta(minutes=self.reserved_duration) + if is_first: + date_planned_start = last_time + date_planned_end = last_time + duration_expected + + else: + date_planned_start = last_time + reserve_time + date_planned_end = date_planned_start + duration_expected + return date_planned_start, date_planned_end, last_time diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index b7aa1adb..460fd994 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -141,11 +141,11 @@ class ResProductMo(models.Model): cutting_tool_coarse_medium_fine = fields.Selection(related='cutting_tool_model_id.integral_coarse_medium_fine', string='粗/中/精') # cutting_tool_model_id.integral_coarse_medium_fine # cutting_tool_run_out_accuracy_max = fields.Float('端跳精度max', digits=(6, 1)) - cutting_tool_run_out_accuracy_max = fields.Char(related='cutting_tool_model_id.integral_run_out_accuracy_max', string='端跳精度max', digits=(6, 1)) + cutting_tool_run_out_accuracy_max = fields.Char(related='cutting_tool_model_id.integral_run_out_accuracy_max', string='端跳精度max') # cutting_tool_model_id.integral_run_out_accuracy_max # cutting_tool_run_out_accuracy_min = fields.Float('端跳精度min', digits=(6, 1)) cutting_tool_run_out_accuracy_min = fields.Char(related='cutting_tool_model_id.integral_run_out_accuracy_min', - string='端跳精度min', digits=(6, 1)) + string='端跳精度min') # cutting_tool_model_id.integral_run_out_accuracy_min # cutting_tool_blade_tip_working_size = fields.Char('刀尖倒角度(°)', size=20) cutting_tool_blade_tip_working_size = fields.Char(related='specification_id.blade_tip_working_size', @@ -777,6 +777,7 @@ class ResProductMo(models.Model): part_number = fields.Char(string='零件图号', readonly=True) machining_drawings = fields.Binary('2D加工图纸', readonly=True) quality_standard = fields.Binary('质检标准', readonly=True) + part_name = fields.Char(string='零件名称', readonly=True) @api.constrains('tool_length') def _check_tool_length_size(self): @@ -849,16 +850,23 @@ class ResProductMo(models.Model): copy_product_id.product_tmpl_id.active = True model_type = self.env['sf.model.type'].search([], limit=1) attachment = self.attachment_create(item['model_name'], item['model_data']) + # 获取坯料冗余配置 + if not item.get('embryo_redundancy'): + embryo_redundancy_id = model_type.embryo_tolerance_id + else: + embryo_redundancy_id = item.get('embryo_redundancy') + if not embryo_redundancy_id: + raise UserError('请先配置模型类型内的坯料冗余') vals = { 'name': '%s-%s-%s' % ('P', order_id.name, i), - 'model_long': item['model_long'] + model_type.embryo_tolerance, - 'model_width': item['model_width'] + model_type.embryo_tolerance, - 'model_height': item['model_height'] + model_type.embryo_tolerance, - 'model_volume': (item['model_long'] + model_type.embryo_tolerance) * ( - item['model_width'] + model_type.embryo_tolerance) * ( - item['model_height'] + model_type.embryo_tolerance), + 'model_long': self.format_float(item['model_long'] + embryo_redundancy_id.long), + 'model_width': self.format_float(item['model_width'] + embryo_redundancy_id.width), + 'model_height': self.format_float(item['model_height'] + embryo_redundancy_id.height), + 'model_volume': self.format_float((item['model_long'] + embryo_redundancy_id.long) * ( + item['model_width'] + embryo_redundancy_id.width) * ( + item['model_height'] + embryo_redundancy_id.height)), 'product_model_type_id': model_type.id, - # 'model_processing_panel': 'R', + 'model_processing_panel': item['processing_panel_detail'], 'model_machining_precision': item['model_machining_precision'], 'model_code': item['barcode'], 'length': item['model_long'], @@ -866,8 +874,8 @@ class ResProductMo(models.Model): 'height': item['model_height'], 'volume': item['model_long'] * item['model_width'] * item['model_height'], 'model_file': '' if not item['model_file'] else base64.b64decode(item['model_file']), - 'model_name': attachment.name, - 'upload_model_file': [(6, 0, [attachment.id])], + 'model_name': attachment.name if attachment else None, + 'upload_model_file': [(6, 0, [attachment.id])] if attachment else None, 'list_price': item['price'], 'materials_id': self.env['sf.production.materials'].search( [('materials_no', '=', item['texture_code'])]).id, @@ -884,6 +892,7 @@ class ResProductMo(models.Model): 'machining_drawings': '' if not item['machining_drawings'] else base64.b64decode( item['machining_drawings']), 'quality_standard': '' if not item['quality_standard'] else base64.b64decode(item['quality_standard']), + 'part_name': item['part_name'], } tax_id = self.env['account.tax'].sudo().search( [('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')]) @@ -898,6 +907,20 @@ class ResProductMo(models.Model): self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id, 'quality_standard', item['quality_standard_mimetype']) return copy_product_id + + def format_float(self, value): + # 将浮点数转换为字符串 + value_str = str(value) + # 检查小数点的位置 + if '.' in value_str: + # 获取小数部分 + decimal_part = value_str.split('.')[1] + # 判断小数位数是否超过2位 + if len(decimal_part) > 2: + # 超过2位则保留2位小数 + return "{:.2f}".format(value) + # 否则保持原来的位数 + return float(value_str) def _get_ids(self, param): type_ids = [] @@ -915,6 +938,8 @@ class ResProductMo(models.Model): return [(6, 0, process_parameters_ids)] def attachment_create(self, name, data): + if not data: + return None attachment = self.env['ir.attachment'].create({ 'datas': base64.b64decode(data), 'type': 'binary', @@ -932,31 +957,40 @@ class ResProductMo(models.Model): # if surface_technology: # no_bom_copy_product_id.route_ids |= surface_technology no_bom_copy_product_id.product_tmpl_id.active = True + logging.info('no_bom_copy_product_id[is_manual_processing]:%s' % no_bom_copy_product_id.is_manual_processing) materials_id = self.env['sf.production.materials'].search( [('materials_no', '=', item['texture_code'])]) materials_type_id = self.env['sf.materials.model'].search( [('materials_no', '=', item['texture_type_code'])]) model_type = self.env['sf.model.type'].search([], limit=1) supplier = self.env['mrp.bom'].get_supplier(materials_type_id) + # 获取坯料冗余配置 + if not item.get('embryo_redundancy_id'): + embryo_redundancy_id = model_type.embryo_tolerance_id + else: + embryo_redundancy_id = item.get('embryo_redundancy_id') + if not embryo_redundancy_id: + raise UserError('请先配置模型类型内的坯料冗余') logging.info('no_bom_copy_product_supplier-vals:%s' % supplier) vals = { 'name': '%s-%s-%s [%s %s-%s * %s * %s]' % ('R', order_id.name, i, materials_id.name, materials_type_id.name, - item['model_long'] + model_type.embryo_tolerance, - item['model_width'] + model_type.embryo_tolerance, - item['model_height'] + model_type.embryo_tolerance), - 'length': item['model_long'] + model_type.embryo_tolerance, - 'width': item['model_width'] + model_type.embryo_tolerance, - 'height': item['model_height'] + model_type.embryo_tolerance, - 'volume': (item['model_long'] + model_type.embryo_tolerance) * ( - item['model_width'] + model_type.embryo_tolerance) * ( - item['model_height'] + model_type.embryo_tolerance), + item['model_long'] + embryo_redundancy_id.long, + item['model_width'] + embryo_redundancy_id.width, + item['model_height'] + embryo_redundancy_id.height), + 'length': item['model_long'] + embryo_redundancy_id.long, + 'width': item['model_width'] + embryo_redundancy_id.width, + 'height': item['model_height'] + embryo_redundancy_id.height, + 'volume': (item['model_long'] + embryo_redundancy_id.long) * ( + item['model_width'] + embryo_redundancy_id.width) * ( + item['model_height'] + embryo_redundancy_id.height), 'embryo_model_type_id': model_type.id, 'list_price': item['price'], 'materials_id': materials_id.id, 'materials_type_id': materials_type_id.id, + 'single_manufacturing': product_id.single_manufacturing, 'is_bfm': True, - 'active': True + 'active': True, } # 外协和采购生成的坯料需要根据材料型号绑定供应商 if route_type == 'subcontract' or route_type == 'purchase': diff --git a/sf_manufacturing/models/quick_easy_order.py b/sf_manufacturing/models/quick_easy_order.py new file mode 100644 index 00000000..89d3a6d8 --- /dev/null +++ b/sf_manufacturing/models/quick_easy_order.py @@ -0,0 +1,71 @@ +from datetime import datetime +from odoo import models + +import logging +import base64 +import hashlib + +_logger = logging.getLogger(__name__) + + +class QuickEasyOrder(models.Model): + _inherit = 'quick.easy.order' + + def distribute_to_factory(self, obj): + """ + 多供货方式,重写派单到工厂 + :return: + """ + try: + _logger.info('---------派单到工厂-------') + res = {'bfm_process_order_list': []} + for item in obj: + attachment = item.upload_model_file[0] + base64_data = base64.b64encode(attachment.datas) + base64_datas = base64_data.decode('utf-8') + barcode = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() + # logging.info('model_file-size: %s' % len(item.model_file)) + res['bfm_process_order_list'].append({ + 'model_long': item.model_length, + 'model_width': item.model_width, + 'model_height': item.model_height, + 'model_volume': item.model_volume, + 'model_machining_precision': item.machining_precision, + 'model_name': attachment.name, + 'model_data': base64_datas, + 'model_file': base64.b64encode(item.model_file).decode('utf-8'), + 'texture_code': item.material_id.materials_no, + 'texture_type_code': item.material_model_id.materials_no, + # 'surface_process_code': self.env['jikimo.surface.process']._json_surface_process_code(item), + 'process_parameters_code': self.env[ + 'sf.production.process.parameter']._json_production_process_item_code( + item), + 'price': item.price, + 'number': item.quantity, + 'total_amount': item.price, + 'remark': '', + 'manual_quotation': True, + 'barcode': barcode, + 'part_number': item.part_drawing_number, + 'machining_drawings_name': '', + 'quality_standard_name': '', + 'machining_drawings_mimetype': '', + 'quality_standard_mimetype': '', + 'machining_drawings': item.machining_drawings, + 'quality_standard': '', + 'part_name': '', + }) + company_id = self.env.ref('base.main_company').sudo() + product_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_default').sudo().with_context(active_test=False).product_variant_id + # user_id = request.env.ref('base.user_admin').sudo() + order_id = self.env['sale.order'].sale_order_create(company_id, 'XXXXX', 'XXXXX', 'XXXXX', + str(datetime.now()), '现结', '支付宝', state='draft') + order_id.default_code = obj.name + i = 1 + for item in res['bfm_process_order_list']: + product = self.env['product.template'].sudo().product_create(product_id, item, order_id, + obj.name, i) + order_id.with_user(self.env.ref("base.user_admin")).sale_order_create_line(product, item) + return order_id + except Exception as e: + return UserError('工厂创建销售订单和产品失败,请联系管理员') diff --git a/sf_manufacturing/models/sale_order.py b/sf_manufacturing/models/sale_order.py new file mode 100644 index 00000000..a56721a2 --- /dev/null +++ b/sf_manufacturing/models/sale_order.py @@ -0,0 +1,166 @@ +import logging +import json +from odoo import models, fields, api +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + state = fields.Selection([ + ('draft', "报价"), + ('sent', "报价已发送"), + ('supply method', "供货方式待确认"), + ('sale', "销售订单"), + ('done', "已锁定"), + ('cancel', "已取消"), + ]) + + def confirm_to_supply_method(self): + self.state = 'supply method' + + def action_confirm(self): + # 判断是否所有产品都选择了供货方式 + filter_line = self.order_line.filtered(lambda line: not line.supply_method) + if filter_line: + raise UserError('当前订单内(%s)产品未选择路线,请选择后重试' % ','.join(filter_line.mapped('product_id.name'))) + + for line in self.order_line: + bom_type = '' + # 根据供货方式修改成品模板 + if line.supply_method == 'automation': + bom_type = 'normal' + product_template_id = self.env.ref('sf_dlm.product_template_sf').sudo().product_tmpl_id + elif line.supply_method == 'outsourcing': + bom_type = 'subcontract' + product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_outsourcing').sudo() + elif line.supply_method == 'purchase': + product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_purchase').sudo() + elif line.supply_method == 'manual': + bom_type = 'normal' + product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo() + + # 复制成品模板上的属性 + line.product_id.product_tmpl_id.copy_template(product_template_id) + # 将模板上的single_manufacturing属性复制到成品上 + line.product_id.single_manufacturing = product_template_id.single_manufacturing + + order_id = self + product = line.product_id + # 拼接方法需要的item结构 + item = { + 'texture_code': product.materials_id.materials_no, + 'texture_type_code': product.materials_type_id.materials_no, + 'model_long': product.length, + 'model_width': product.width, + 'model_height': product.height, + 'price': product.list_price, + 'embryo_redundancy_id': line.embryo_redundancy_id, + } + # 获取成品名结尾-n的n + product_seria = int(product.name.split('-')[-1]) + # 成品供货方式为采购则不生成bom + if line.supply_method != 'purchase': + bom_data = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).get_bom(product) + _logger.info('bom_data:%s' % bom_data) + if bom_data: + bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create(product, 'normal', False) + bom.with_user(self.env.ref("base.user_admin")).bom_create_line_has(bom_data) + else: + # 当成品上带有客供料选项时,生成坯料时选择“客供料”路线 + if line.embryo_redundancy_id: + # 将成品模板的内容复制到成品上 + customer_provided_embryo = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo() + # 创建坯料,客供料的批量不需要创建bom + material_customer_provided_embryo = self.env['product.template'].sudo().no_bom_product_create( + customer_provided_embryo.with_context(active_test=False).product_variant_id, + item, + order_id, 'material_customer_provided', product_seria, product) + # 成品配置bom + product_bom_material_customer_provided = self.env['mrp.bom'].with_user( + self.env.ref("base.user_admin")).bom_create( + product, bom_type, 'product') + product_bom_material_customer_provided.with_user(self.env.ref("base.user_admin")).bom_create_line_has( + material_customer_provided_embryo) + elif line.product_id.materials_type_id.gain_way == '自加工': + self_machining_id = self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo() + # 创建坯料 + self_machining_embryo = self.env['product.template'].sudo().no_bom_product_create( + self_machining_id, + item, + order_id, 'self_machining', product_seria, product) + # 创建坯料的bom + self_machining_bom = self.env['mrp.bom'].with_user( + self.env.ref("base.user_admin")).bom_create( + self_machining_embryo, 'normal', False) + # 创建坯料里bom的组件 + self_machining_bom_line = self_machining_bom.with_user( + self.env.ref("base.user_admin")).bom_create_line( + self_machining_embryo) + if not self_machining_bom_line: + raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配') + # 产品配置bom + product_bom_self_machining = self.env['mrp.bom'].with_user( + self.env.ref("base.user_admin")).bom_create( + product, bom_type, 'product') + product_bom_self_machining.with_user(self.env.ref("base.user_admin")).bom_create_line_has( + self_machining_embryo) + elif line.product_id.materials_type_id.gain_way == '外协': + outsource_id = self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo() + # 创建坯料 + outsource_embryo = self.env['product.template'].sudo().no_bom_product_create(outsource_id, + item, + order_id, + 'subcontract', + product_seria, product) + if outsource_embryo == -3: + raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配') + # 创建坯料的bom + outsource_bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create( + outsource_embryo, + 'subcontract', True) + # 创建坯料的bom的组件 + outsource_bom_line = outsource_bom.with_user( + self.env.ref("base.user_admin")).bom_create_line(outsource_embryo) + if not outsource_bom_line: + raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配') + # 产品配置bom + product_bom_outsource = self.env['mrp.bom'].with_user( + self.env.ref("base.user_admin")).bom_create(product, bom_type, 'product') + product_bom_outsource.with_user(self.env.ref("base.user_admin")).bom_create_line_has( + outsource_embryo) + elif line.product_id.materials_type_id.gain_way == '采购': + purchase_id = self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo() + purchase_embryo = self.env['product.template'].sudo().no_bom_product_create(purchase_id, + item, + order_id, + 'purchase', product_seria, + product) + if purchase_embryo == -3: + raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配') + else: + # 产品配置bom + product_bom_purchase = self.env['mrp.bom'].with_user( + self.env.ref("base.user_admin")).bom_create(product, bom_type, 'product') + product_bom_purchase.with_user(self.env.ref("base.user_admin")).bom_create_line_has( + purchase_embryo) + return super(SaleOrder, self).action_confirm() + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + # 供货方式 + supply_method = fields.Selection([ + ('automation', "自动化产线加工"), + ('manual', "人工线下加工"), + ('purchase', "外购"), + ('outsourcing', "委外加工"), + ], string='供货方式') + + def write(self, vals): + if 'supply_method' in vals: + for line in self: + if vals['supply_method'] == 'automation' and line.manual_quotation: + raise UserError('当前(%s)产品为人工编程产品,不能选择自动化产线加工' % ','.join(line.mapped('product_id.name'))) + return super(SaleOrderLine, self).write(vals) diff --git a/sf_manufacturing/models/sf_production_common.py b/sf_manufacturing/models/sf_production_common.py new file mode 100644 index 00000000..113858c1 --- /dev/null +++ b/sf_manufacturing/models/sf_production_common.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +import logging +from odoo import fields, models, api +from odoo.exceptions import UserError + + +class SfProductionProcessParameter(models.Model): + _inherit = 'sf.production.process.parameter' + + @api.model + def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): + if self._context.get('route_id'): + parameter = [] + routing = self.env['mrp.routing.workcenter'].search([('id', '=', self._context.get('route_id'))]) + technology_design = self.env['sf.technology.design'].search( + [('production_id', '=', self._context.get('production_id')), ('routing_tag', '=', 'special'), + ('route_id', '=', self._context.get('route_id'))]) + for t in technology_design: + if t.process_parameters_id: + parameter.append(t.process_parameters_id.id) + 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) diff --git a/sf_manufacturing/models/sf_technology_design.py b/sf_manufacturing/models/sf_technology_design.py new file mode 100644 index 00000000..392aa021 --- /dev/null +++ b/sf_manufacturing/models/sf_technology_design.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models, api, _ +from odoo.exceptions import ValidationError + + +class sf_technology_design(models.Model): + _name = 'sf.technology.design' + _description = "工艺设计" + + sequence = fields.Integer('序号') + route_id = fields.Many2one('mrp.routing.workcenter', '工序') + process_parameters_id = fields.Many2one('sf.production.process.parameter', string='表面工艺参数') + panel = fields.Char('加工面') + routing_tag = fields.Selection(related='route_id.routing_tag', string='标签', store=True) + time_cycle_manual = fields.Float(related='route_id.time_cycle_manual', string='预计时长') + production_id = fields.Many2one('mrp.production') + is_auto = fields.Boolean('是否自动生成', default=False) + active = fields.Boolean('有效', default=True) + + def json_technology_design_str(self, k, route, i, process_parameter): + workorders_values_str = [0, '', { + 'route_id': route.id if route.routing_type in ['表面工艺'] else route.route_workcenter_id.id, + 'panel': k, + 'process_parameters_id': False if route.routing_type != '表面工艺' else self.env[ + 'sf.production.process.parameter'].search( + [('id', '=', process_parameter.id)]).id, + 'sequence': i, + 'is_auto': True}] + return workorders_values_str + + def unlink_technology_design(self): + self.active = False + + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if not vals.get('route_id'): + raise ValidationError(_("工序不能为空")) + return super(sf_technology_design, self).create(vals_list) diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 40d159d5..01c7bd93 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -208,6 +208,15 @@ class StockRule(models.Model): '''创建制造订单''' productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( productions_values) + # 将这一批制造订单的采购组根据成品设置为不同的采购组 + product_group_id = {} + for index, production in enumerate(productions): + if production.product_id.id not in product_group_id.keys(): + product_group_id[production.product_id.id] = production.procurement_group_id.id + else: + productions_values[index].update({'name': production.name}) + procurement_group_vals = production._prepare_procurement_group_vals(productions_values[index]) + production.procurement_group_id = self.env["procurement.group"].create(procurement_group_vals).id # self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) @@ -272,18 +281,26 @@ class StockRule(models.Model): workorder_duration += workorder.duration_expected sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) - if sale_order: - # sale_order.write({'schedule_status': 'to schedule'}) - self.env['sf.production.plan'].sudo().with_company(company_id).create({ - 'name': production.name, - 'order_deadline': sale_order.deadline_of_delivery, - 'production_id': production.id, - 'date_planned_start': production.date_planned_start, - 'origin': production.origin, - 'product_qty': production.product_qty, - 'product_id': production.product_id.id, - 'state': 'draft', - }) + # 如果订单为空,则获取来源制造订单的销售单 + if not sale_order: + mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)], + limit=1) + if mrp_production: + sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)]) + else: + mrp_production = production + # if sale_order: + # sale_order.write({'schedule_status': 'to schedule'}) + self.env['sf.production.plan'].sudo().with_company(company_id).create({ + 'name': production.name, + 'order_deadline': sale_order.deadline_of_delivery, + 'production_id': production.id, + 'date_planned_start': production.date_planned_start, + 'origin': mrp_production.origin, + 'product_qty': production.product_qty, + 'product_id': production.product_id.id, + 'state': 'draft', + }) all_production = productions grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)} # 初始化一个字典来存储每个product_id对应的生产订单名称列表 @@ -293,78 +310,86 @@ class StockRule(models.Model): # 为同一个product_id创建一个生产订单名称列表 product_id_to_production_names[product_id] = [production.name for production in all_production] for production_item in productions: - + technology_design_values = [] production_programming = self.env['mrp.production'].search( [('product_id.id', '=', production_item.product_id.id), ('origin', '=', production_item.origin)], limit=1, order='id asc') if production_item.product_id.id in product_id_to_production_names: - if not production_programming.programming_no: - if production_item.product_id.model_process_parameters_ids: - is_purchase = False - sorted_process_parameters = sorted(production_item.product_id.model_process_parameters_ids, - key=lambda w: w.id) - - consecutive_process_parameters = [] - m = 0 - for i in range(len(sorted_process_parameters) - 1): - if m == 0: - is_purchase = False - if self.env['product.template']._get_process_parameters_product( - sorted_process_parameters[i]).partner_id == self.env[ - 'product.template']._get_process_parameters_product(sorted_process_parameters[ - i + 1]).partner_id and \ - sorted_process_parameters[i].gain_way == '外协': - if sorted_process_parameters[i] not in consecutive_process_parameters: - consecutive_process_parameters.append(sorted_process_parameters[i]) - consecutive_process_parameters.append(sorted_process_parameters[i + 1]) - m += 1 - continue - else: - if m == len(consecutive_process_parameters) - 1 and m != 0: - self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, - production_item, - product_id_to_production_names) - if sorted_process_parameters[i] in consecutive_process_parameters: - is_purchase = True - consecutive_process_parameters = [] - m = 0 - # 当前面的连续外协采购单生成再生成当前外协采购单 - if is_purchase is False: - self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, - production_item, - product_id_to_production_names) - if m == len(consecutive_process_parameters) - 1 and m != 0: - self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, - production_item, - product_id_to_production_names) - if sorted_process_parameters[i] in consecutive_process_parameters: - is_purchase = True - consecutive_process_parameters = [] - m = 0 - if m == len(consecutive_process_parameters) - 1 and m != 0: - self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, - production_item, - product_id_to_production_names) - if is_purchase is False and m == 0: - if len(sorted_process_parameters) == 1: - self.env['purchase.order'].get_purchase_order(sorted_process_parameters, - production_item, - product_id_to_production_names) - else: - self.env['purchase.order'].get_purchase_order(sorted_process_parameters[i], - production_item, - product_id_to_production_names) - # # 同一个产品多个制造订单对应一个编程单和模型库 - # # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 - if not production_item.programming_no: + # 同一个产品多个制造订单对应一个编程单和模型库 + # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 + if not production_item.programming_no and production_item.production_type == '自动化产线加工': if not production_programming.programming_no: production_item.fetchCNC( ', '.join(product_id_to_production_names[production_item.product_id.id])) else: production_item.write({'programming_no': production_programming.programming_no, 'programming_state': '编程中'}) - return True + i = 0 + if production_item.product_id.categ_id.type == '成品': + # 根据加工面板的面数及成品工序模板生成工序设计 + if production_item.production_type == '自动化产线加工': + model = 'sf.product.model.type.routing.sort' + domain = [ + ('product_model_type_id', '=', production_item.product_id.product_model_type_id.id)] + else: + model = 'sf.manual.product.model.type.routing.sort' + domain = [('manual_product_model_type_id', '=', + production_item.product_id.product_model_type_id.id)] + product_routing_workcenter = self.env[model].search(domain, order='sequence asc') + if production_item.production_type == '自动化产线加工': + for k in (production_item.product_id.model_processing_panel.split(',')): + for route in product_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(k, route, i, False)) + else: + for route in product_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(False, route, i, False)) + elif production_item.product_id.categ_id.type == '坯料': + embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search( + [('embryo_model_type_id', '=', production_item.product_id.embryo_model_type_id.id)], + order='sequence asc' + ) + for route_embryo in embryo_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, + False)) + surface_technics_arr = [] + route_workcenter_arr = [] + for item in production_item.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: + if item.route_workcenter_id.surface_technics_id.id: + for process_param in production_item.product_id.model_process_parameters_ids: + if item.route_workcenter_id.surface_technics_id == process_param.process_id: + surface_technics_arr.append( + item.route_workcenter_id.surface_technics_id.id) + route_workcenter_arr.append(item.route_workcenter_id.id) + if surface_technics_arr: + production_process = self.env['sf.production.process'].search( + [('id', 'in', surface_technics_arr)], + order='sequence asc' + ) + for p in production_process: + logging.info('production_process:%s' % p.name) + process_parameter = production_item.product_id.model_process_parameters_ids.filtered( + lambda pm: pm.process_id.id == p.id) + if process_parameter: + i += 1 + route_production_process = self.env[ + 'mrp.routing.workcenter'].search( + [('surface_technics_id', '=', p.id), + ('id', 'in', route_workcenter_arr)]) + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(False, + route_production_process, + i, + process_parameter)) + production_item.technology_design_ids = technology_design_values + productions.write({'state': 'technology_to_confirmed'}) + return True class ProductionLot(models.Model): @@ -398,6 +423,8 @@ class ProductionLot(models.Model): """Generate `lot_names` from a string.""" if first_lot.__contains__(display_name): first_lot = first_lot[(len(display_name) + 1):] + else: + first_lot = first_lot[-3:] # We look if the first lot contains at least one digit. caught_initial_number = regex_findall(r"\d+", first_lot) @@ -452,6 +479,14 @@ class ProductionLot(models.Model): if product.categ_id.name == '刀具': return self.env['stock.lot'].get_tool_generate_lot_names1(company, product) else: + # 对last_serial的name进行检测,如果不是以产品名称+数字的形式的就重新搜索 + if product.name.split('[')[0] not in last_serial.name: + last_serial = self.env['stock.lot'].search( + [('company_id', '=', company.id), ('product_id', '=', product.id), + ('name', 'ilike', product.name.split('[')[0])], + limit=1, order='name desc') + if not last_serial: + return "%s-%03d" % (product.name, 1) return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name, 2)[1] now = datetime.now().strftime("%Y%m%d") if product.cutting_tool_model_id: @@ -548,6 +583,45 @@ class StockPicking(models.Model): _inherit = 'stock.picking' surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数") + person_of_delivery = fields.Char('收货人', compute='_compute_move_ids', store=True) + telephone_of_delivery = fields.Char('电话号码', compute='_compute_move_ids', store=True) + address_of_delivery = fields.Char('联系地址', compute='_compute_move_ids', store=True) + + retrospect_ref = fields.Char('追溯参考', compute='_compute_move_ids', store=True) + + picking_type_sequence_code = fields.Char(related='picking_type_id.sequence_code') + + @api.depends('move_ids', 'move_ids.product_id') + def _compute_move_ids(self): + for item in self: + if item.move_ids: + if item.picking_type_id.sequence_code == 'DL': + sale_name = item.move_ids[0].product_id.name.split('-')[1] + if 'S' in sale_name: + sale_id = self.env['sale.order'].sudo().search([('name', '=', sale_name)]) + item.person_of_delivery = sale_id.person_of_delivery + item.telephone_of_delivery = sale_id.telephone_of_delivery + item.address_of_delivery = sale_id.address_of_delivery + else: + raise ValidationError('坯料名称格式错误,正确格式为[R-S???-?]!!!') + move_ids = [] + for move_id in item.move_ids: + move_ids.append(move_id.product_id.id) + boms = self.env['mrp.bom'].sudo().search([('bom_line_ids.product_id', 'in', move_ids)]) + if boms: + codes_list = [] + for bom in boms: + if bom.product_tmpl_id.default_code: + code_list = bom.product_tmpl_id.default_code.split('-') + if len(code_list) >= 4: + code = '-'.join(code_list[:4]) + if code not in codes_list: + codes_list.append(code) + else: + raise ValidationError('坯料成品的内部参考值格式错误') + item.retrospect_ref = ','.join(codes_list) + elif item.picking_type_id.sequence_code in ['INT', 'PC']: + pass # 设置外协出入单的名称 def _get_name_Res(self, rescode): @@ -561,84 +635,79 @@ class StockPicking(models.Model): return '%s%s' % (rescode, num) def button_validate(self): - move_out = self.env['stock.move'].search( - [('location_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), - ('location_dest_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'VL-SPOC')]).id), - ('origin', '=', self.origin)]) - # if self.id == move_out.picking_id.id: - # if move_out.move_line_ids.workorder_id.state not in ['progress']: - # raise UserError( - # _('该出库单里源单据内的单号为%s的工单还未开始,不能进行验证操作!' % move_out.move_line_ids.workorder_id.name)) - # 入库单验证 - move_in = self.env['stock.move'].search( - [('location_dest_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), - ('location_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'VL-SPOC')]).id), - ('origin', '=', self.origin), ('picking_id', '=', self.id)]) - if self.location_id == move_in.location_id and self.location_dest_id == move_in.location_dest_id: - if move_out.origin == move_in.origin: - move_in.write({'production_id': False}) - if move_out.picking_id.state != 'done': - raise UserError( - _('该入库单对应的单号为%s的出库单还未完成,不能进行验证操作!' % move_out.picking_id.name)) res = super().button_validate() - if res is True: - if self.id == move_out.picking_id.id: - # if move_out.move_line_ids.workorder_id.state == 'progress': - move_in = self.env['stock.move'].search( - [('location_dest_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), - ('location_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'VL-SPOC')]).id), - ('origin', '=', self.origin)]) - production = self.env['mrp.production'].search([('name', '=', self.origin)]) - if move_in.state != 'done': - move_in.write({'state': 'assigned'}) - self.env['stock.move.line'].create(move_in.get_move_line(production, None)) - + picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id + if res is True and self.picking_type_id.id == picking_type_in: + # 如果是最后一张外协入库单,则设置库存位置的预留数量 + move_in = self.move_ids + if move_in: + workorder = move_in.subcontract_workorder_id + workorders = workorder.production_id.workorder_ids + subcontract_workorders = workorders.filtered(lambda wo: wo.is_subcontract == True).sorted('sequence') + if workorder == subcontract_workorders[-1]: + self.env['stock.quant']._update_reserved_quantity( + move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty, lot_id=move_in.move_line_ids.lot_id, + package_id=False, owner_id=False, strict=False + ) + return res # 创建 外协出库入单 - def create_outcontract_picking(self, sorted_workorders_arr, item): - m = 0 - for sorted_workorders in sorted_workorders_arr: - # pick_ids = [] - if m == 0: - outcontract_stock_move = self.env['stock.move'].search( - [('workorder_id', '=', sorted_workorders.id), ('production_id', '=', item.id)]) - if not outcontract_stock_move: - new_picking = True - location_id = self.env['stock.location'].search( - [('barcode', 'ilike', 'VL-SPOC')]).id, - location_dest_id = self.env['stock.location'].search( - [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id, - outcontract_picking_type_in = self.env.ref( - 'sf_manufacturing.outcontract_picking_in').id, - outcontract_picking_type_out = self.env.ref( - 'sf_manufacturing.outcontract_picking_out').id, - moves_out = self.env['stock.move'].sudo().create( - self.env['stock.move']._get_stock_move_values_Res(item, location_dest_id, location_id, - outcontract_picking_type_out)) - picking_out = self.create( - moves_out._get_new_picking_values_Res(item, sorted_workorders, 'WH/OCOUT/')) - # pick_ids.append(picking_out.id) - moves_out.write( - {'picking_id': picking_out.id, 'state': 'waiting', 'workorder_id': sorted_workorders.id}) - moves_out._assign_picking_post_process(new=new_picking) - moves_in = self.env['stock.move'].sudo().create( - self.env['stock.move']._get_stock_move_values_Res(item, location_id, location_dest_id, - outcontract_picking_type_in)) - picking_in = self.create( - moves_in._get_new_picking_values_Res(item, sorted_workorders, 'WH/OCIN/')) - # pick_ids.append(picking_in.id) - moves_in.write( - {'picking_id': picking_in.id, 'state': 'waiting', 'workorder_id': sorted_workorders.id}) - moves_in._assign_picking_post_process(new=new_picking) - m += 1 - # sorted_workorders.write({'picking_ids': [(6, 0, pick_ids)]}) + def create_outcontract_picking(self, workorders, item, sorted_workorders): + for workorder in workorders: + if workorder.move_subcontract_workorder_ids: + workorder.move_subcontract_workorder_ids.write({'state': 'draft'}) + workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'draft'}) + else: + # 创建一个新的补货组 + procurement_group_id = self.env['procurement.group'].create({ + 'name': workorder.name, + 'partner_id': self.partner_id.id, + }) + move_dest_id = False + # 如果当前工单是是制造订单的最后一个工单 + if workorder == item.workorder_ids[-1]: + move_dest_id = item.move_raw_ids[0].id + else: + # 从sorted_workorders中找到上一工单的move + if sorted_workorders.index(workorder) > 0: + move_dest_id = sorted_workorders[sorted_workorders.index(workorder) - 1].move_subcontract_workorder_ids[1].id + new_picking = True + outcontract_picking_type_in = self.env.ref( + 'sf_manufacturing.outcontract_picking_in').id, + outcontract_picking_type_out = self.env.ref( + 'sf_manufacturing.outcontract_picking_out').id, + moves_in = self.env['stock.move'].sudo().create( + self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in, procurement_group_id.id, move_dest_id)) + picking_in = self.create( + moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/')) + # pick_ids.append(picking_in.id) + moves_in.write( + {'picking_id': picking_in.id, 'state': 'waiting'}) + moves_in._assign_picking_post_process(new=new_picking) + moves_out = self.env['stock.move'].sudo().create( + self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out, procurement_group_id.id, moves_in.id)) + workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]}) + picking_out = self.create( + moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/')) + # pick_ids.append(picking_out.id) + moves_out.write( + {'picking_id': picking_out.id, 'state': 'waiting'}) + moves_out._assign_picking_post_process(new=new_picking) + + + @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id') + def _compute_state(self): + super(StockPicking, self)._compute_state() + for picking in self: + # 外协出库单根据工单状态,采购单状态来确定 + picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_out').id + if picking.picking_type_id.id == picking_type_id: + if picking.move_ids: + workorder = picking.move_ids[0].subcontract_workorder_id + if picking.state == 'assigned': + if workorder.state in ['pending', 'waiting'] or workorder._get_surface_technics_purchase_ids().state in ['draft', 'sent']: + picking.state = 'waiting' class ReStockMove(models.Model): @@ -648,17 +717,20 @@ class ReStockMove(models.Model): materiel_width = fields.Float(string='物料宽度', digits=(16, 4)) materiel_height = fields.Float(string='物料高度', digits=(16, 4)) - def _get_stock_move_values_Res(self, item, location_src_id, location_dest_id, picking_type_id): - route = self.env['stock.route'].sudo().search([('name', '=', '表面工艺外协')]) + def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False): + route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id + stock_rule = self.env['stock.rule'].sudo().search([('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)]) move_values = { 'name': '推', 'company_id': item.company_id.id, 'product_id': item.bom_id.bom_line_ids.product_id.id, 'product_uom': item.bom_id.bom_line_ids.product_uom_id.id, 'product_uom_qty': 1.0, - 'location_id': location_src_id, - 'location_dest_id': location_dest_id, + 'location_id': stock_rule.location_src_id.id, + 'location_dest_id': stock_rule.location_dest_id.id, 'origin': item.name, + 'group_id': group_id, + 'move_dest_ids': [(6, 0, [move_dest_ids])] if move_dest_ids else False, # 'route_ids': False if not route else [(4, route.id)], 'date_deadline': datetime.now(), 'picking_type_id': picking_type_id, @@ -682,7 +754,7 @@ class ReStockMove(models.Model): 'picking_type_id': picking_type_id, 'location_id': self.mapped('location_id').id, 'location_dest_id': self.mapped('location_dest_id').id, - 'state': 'confirmed', + 'state': 'waiting', } def get_move_line(self, production_id, sorted_workorders): @@ -695,7 +767,7 @@ class ReStockMove(models.Model): 'picking_id': self.picking_id.id, 'reserved_uom_qty': 1.0, 'lot_id': production_id.move_line_raw_ids.lot_id.id, - 'company_id': self.company_id.id, + 'company_id': self.env.company.id, # 'workorder_id': '' if not sorted_workorders else sorted_workorders.id, # 'production_id': '' if not sorted_workorders else sorted_workorders.production_id.id, 'state': 'assigned', @@ -739,6 +811,8 @@ class ReStockMove(models.Model): self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) else: self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) + if self.picking_type_id.sequence_code == 'DL' and not self.move_line_nosuggest_ids: + self.action_assign_serial_show_details() elif self.product_id.tracking == "lot": self._put_tool_lot(self.company_id, self.product_id, self.origin) @@ -891,6 +965,40 @@ class ReStockMove(models.Model): qty_by_location[loc.id] += 1 return move_lines_commands + def _merge_moves_fields(self): + """ + 合并制造订单的完成move单据 + """ + res = super(ReStockMove, self)._merge_moves_fields() + if self[0].origin and self.picking_type_id.name in ['生产发料', '内部调拨', '生产入库', '客供料入库']: + production = self.env['mrp.production'].search([('name', '=', self[0].origin)], limit=1, order='id asc') + productions = self.env['mrp.production'].search( + [('origin', '=', production.origin), ('product_id', '=', production.product_id.id)]) + res['origin'] = ','.join(productions.mapped('name')) + return res + + def _get_new_picking_values(self): + """ + 创建调拨单时,在此新增或修改调拨单的数据 + """ + res = super(ReStockMove, self)._get_new_picking_values() + ## 制造订单报废生成的新制造订单不走合并 + production_remanufacture = None + if 'origin' in res: + if self.picking_type_id.name in ['生产发料', '内部调拨']: + production_remanufacture = self.env['mrp.production'].search( + [('name', '=', res['origin']), ('is_remanufacture', '=', True)]) + if not production_remanufacture: + if self[0].origin and self.picking_type_id.name in ['生产发料', '内部调拨']: + production = self.env['mrp.production'].search([('name', '=', self[0].origin)], limit=1, order='id asc') + productions = self.env['mrp.production'].search( + [('origin', '=', production.origin), ('product_id', '=', production.product_id.id)]) + res['origin'] = ','.join(productions.mapped('name')) + res['retrospect_ref'] = production.product_id.name + return res + + subcontract_workorder_id = fields.Many2one('mrp.workorder', '外协工单组件', check_company=True, index='btree_not_null') + class ReStockQuant(models.Model): _inherit = 'stock.quant' diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index be71cb0d..2880e662 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -11,11 +11,11 @@ access_sf_model_type_group_sale_director,sf_model_type_group_sale_director,model access_sf_model_type_group_purchase_director,sf_model_type_group_purchase_director,model_sf_model_type,sf_base.group_purchase_director,1,0,0,0 access_sf_model_type_group_plan_director,sf_model_type_group_plan_director,model_sf_model_type,sf_base.group_plan_director,1,0,0,0 access_sf_product_model_type_routing_sort_group_sf_mrp_user,sf_product_model_type_routing_sort,model_sf_product_model_type_routing_sort,sf_base.group_sf_mrp_user,1,0,0,0 -access_sf_product_model_type_routing_sort_manager,sf_product_model_type_routing_sort,model_sf_product_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,0 +access_sf_product_model_type_routing_sort_manager,sf_product_model_type_routing_sort,model_sf_product_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,1 access_sf_embryo_model_type_routing_sort_group_sf_mrp_user,sf_embryo_model_type_routing_sort,model_sf_embryo_model_type_routing_sort,sf_base.group_sf_mrp_user,1,0,0,0 -access_sf_embryo_model_type_routing_sort_manager,sf_embryo_model_type_routing_sort,model_sf_embryo_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,0 +access_sf_embryo_model_type_routing_sort_manager,sf_embryo_model_type_routing_sort,model_sf_embryo_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,1 access_sf_surface_technics_model_type_routing_sort,sf_surface_technics_model_type_routing_sort,model_sf_surface_technics_model_type_routing_sort,sf_base.group_sf_mrp_user,1,0,0,0 -access_sf_surface_technics_model_type_routing_sort_manager,sf_surface_technics_model_type_routing_sort,model_sf_surface_technics_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,0 +access_sf_surface_technics_model_type_routing_sort_manager,sf_surface_technics_model_type_routing_sort,model_sf_surface_technics_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,1 access_sf_production_line_group_sf_mrp_user,sf.production.line,model_sf_production_line,sf_base.group_sf_mrp_user,1,1,1,0 access_sf_production_line_manager,sf.production.line,model_sf_production_line,sf_base.group_sf_mrp_manager,1,1,1,0 access_maintenance_equipment_tool_group_sf_mrp_user,maintenance_equipment_tool,model_maintenance_equipment_tool,sf_base.group_sf_mrp_user,1,0,0,0 @@ -52,6 +52,7 @@ access_mrp_routing_workcenter_manager_group_sf_mrp_user,mrp.routing.workcenter.m access_mrp_bom_manager_group_sf_mrp_user,mrp.bom.manager,mrp.model_mrp_bom,sf_base.group_sf_mrp_user,1,1,1,0 access_mrp_bom_line_manager_group_sf_mrp_user,mrp.bom.line.manager,mrp.model_mrp_bom_line,sf_base.group_sf_mrp_user,1,1,1,0 access_mrp_bom_line_group_plan_director,mrp_bom_line_group_plan_director,mrp.model_mrp_bom_line,sf_base.group_plan_director,1,1,1,0 +access_mrp_bom_line_group_sf_stock_user,mrp_bom_line_group_sf_stock_user,mrp.model_mrp_bom_line,sf_base.group_sf_stock_user,1,1,1,0 access_mrp_bom_line_group_sale_director,mrp_bom_line_group_sale_director,mrp.model_mrp_bom_line,sf_base.group_sale_director,1,1,1,0 access_mrp_bom_line_group_sale_salemanager,mrp_bom_line_group_sale_salemanager,mrp.model_mrp_bom_line,sf_base.group_sale_salemanager,1,0,1,0 access_mrp_bom_line_group_purchase_director,mrp_bom_line_group_purchase_director,mrp.model_mrp_bom_line,sf_base.group_purchase_director,1,1,1,0 @@ -116,7 +117,7 @@ access_mrp_production_group_quality,mrp_production,model_mrp_production,sf_base. access_mrp_production_group_quality_director,mrp_production,model_mrp_production,sf_base.group_quality_director,1,1,0,0 access_mrp_workorder_group_quality,mrp_workorder,model_mrp_workorder,sf_base.group_quality,1,1,0,0 access_mrp_workorder_group_quality_director,mrp_workorder,model_mrp_workorder,sf_base.group_quality_director,1,1,0,0 -access_mrp_workorder,mrp_workorder,model_mrp_workorder,sf_base.group_plan_dispatch,1,1,0,0 +access_mrp_workorder,mrp_workorder,model_mrp_workorder,sf_base.group_plan_dispatch,1,1,1,0 access_sf_production_line_group_plan_dispatch,sf.production.line,model_sf_production_line,sf_base.group_plan_dispatch,1,0,0,0 access_sf_production_line_group_plan_director,sf.production.line,model_sf_production_line,sf_base.group_plan_director,1,1,1,0 access_sf_production_line,sf.production.line,model_sf_production_line,sf_maintenance.sf_group_equipment_user,1,1,1,0 @@ -165,7 +166,19 @@ access_sf_agv_scheduling_group_sf_order_user,sf_agv_scheduling_group_sf_order_us access_sf_agv_scheduling_group_sf_mrp_manager,sf_agv_scheduling_group_sf_mrp_manager,model_sf_agv_scheduling,sf_base.group_sf_mrp_manager,1,1,1,0 access_sf_agv_scheduling_group_sf_equipment_user,sf_agv_scheduling_group_sf_equipment_user,model_sf_agv_scheduling,sf_base.group_sf_equipment_user,1,1,1,0 +access_sf_technology_design_group_plan_dispatch,sf_technology_design_group_plan_dispatch,model_sf_technology_design,sf_base.group_plan_dispatch,1,1,1,0 +access_sf_technology_design_group_sf_mrp_manager,sf_technology_design_group_sf_mrp_manager,model_sf_technology_design,sf_base.group_sf_mrp_manager,1,0,0,0 +access_sf_technology_design_group_production_engineer,sf_technology_design_group_production_engineer,model_sf_technology_design,sf_base.group_production_engineer,1,1,1,0 +access_sf_technology_design_group_sf_equipment_user,sf_technology_design_group_sf_equipment_user,model_sf_technology_design,sf_base.group_sf_equipment_user,1,0,0,0 +access_sf_technology_design_group_sf_order_user,sf_technology_design_group_sf_order_user,model_sf_technology_design,sf_base.group_sf_order_user,1,0,0,0 +access_sf_production_technology_wizard_group_plan_dispatch,sf_production_technology_wizard_group_plan_dispatch,model_sf_production_technology_wizard,sf_base.group_plan_dispatch,1,1,1,0 +access_sf_production_technology_wizard_group_sf_mrp_manager,sf_production_technology_wizard_group_sf_mrp_manager,model_sf_production_technology_wizard,sf_base.group_sf_mrp_manager,1,0,0,0 +access_sf_production_technology_wizard_group_production_engineer,sf_production_technology_wizard_group_production_engineer,model_sf_production_technology_wizard,sf_base.group_production_engineer,1,1,1,0 +access_sf_production_technology_re_adjust_wizard_group_plan_dispatch,sf_production_technology_re_adjust_wizard_group_plan_dispatch,model_sf_production_technology_re_adjust_wizard,sf_base.group_plan_dispatch,1,1,1,0 +access_sf_production_technology_re_adjust_wizard_group_sf_mrp_manager,sf_production_technology_re_adjust_wizard_group_sf_mrp_manager,model_sf_production_technology_re_adjust_wizard,sf_base.group_sf_mrp_manager,1,1,1,0 +access_sf_production_technology_re_adjust_wizard_group_production_engineer,sf_production_technology_re_adjust_wizard_group_production_engineer,model_sf_production_technology_re_adjust_wizard,sf_base.group_production_engineer,1,1,1,0 - - - +access_sf_manual_product_model_type_routing_sort_group_sf_mrp_user,sf_manual_product_model_type_routing_sort,model_sf_manual_product_model_type_routing_sort,sf_base.group_sf_mrp_user,1,0,0,0 +access_sf_manual_product_model_type_routing_sort_manager,sf_manual_product_model_type_routing_sort,model_sf_manual_product_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,1 +access_sf_manual_product_model_type_routing_sort_group_plan_dispatch,sf_manual_product_model_type_routing_sort_group_plan_dispatch,model_sf_manual_product_model_type_routing_sort,sf_base.group_plan_dispatch,1,0,0,0 +access_sf_detection_result_manager,sf_detection_result_manager,model_sf_detection_result,,1,1,1,1 diff --git a/sf_manufacturing/views/model_type_view.xml b/sf_manufacturing/views/model_type_view.xml index 09877a83..0f8b3085 100644 --- a/sf_manufacturing/views/model_type_view.xml +++ b/sf_manufacturing/views/model_type_view.xml @@ -31,7 +31,7 @@

- + @@ -44,6 +44,17 @@ + + + + + + + + + + + diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index 4c06c544..fd0a5ead 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -35,9 +35,10 @@ - - - + + + @@ -56,6 +57,16 @@ + + + + + 1 + + @@ -70,9 +81,15 @@ - confirmed,pending_cam,progress,rework,scrap,done + technology_to_confirmed,confirmed,pending_cam,progress,rework,scrap,done + + 1 + + + 1 + @@ -80,9 +97,13 @@ - - + + + @@ -97,8 +118,10 @@ - - + + + @@ -122,6 +145,10 @@ groups="sf_base.group_sf_mrp_user"/> + + + + + + + + + + True + + + True + + + 子MO @@ -517,8 +581,9 @@ - @@ -673,5 +738,19 @@ + + + + \ No newline at end of file diff --git a/sf_manufacturing/views/mrp_routing_workcenter_view.xml b/sf_manufacturing/views/mrp_routing_workcenter_view.xml index eada92d9..ddb9558c 100644 --- a/sf_manufacturing/views/mrp_routing_workcenter_view.xml +++ b/sf_manufacturing/views/mrp_routing_workcenter_view.xml @@ -16,6 +16,7 @@ + diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index 8d3c6c4f..be1a6523 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -32,8 +32,11 @@ - + + @@ -136,7 +139,7 @@