From 92f591c3e7a44f004bca4642b388beefab8ec95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Wed, 18 Jun 2025 09:25:44 +0800 Subject: [PATCH 01/48] =?UTF-8?q?=E5=B0=86sf=5Fmanufacturing=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E4=B8=ADstock.py=E7=9A=84=5Frun=5Fmanufacture?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E8=BF=9B=E8=A1=8C=E6=8B=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/mrp_production.py | 72 +++- sf_manufacturing/models/stock.py | 429 +++++++++++----------- sf_mrs_connect/models/__init__.py | 3 +- sf_mrs_connect/models/mrp_production.py | 44 +++ sf_plan/models/__init__.py | 1 + sf_plan/models/mrp_production.py | 41 +++ 6 files changed, 368 insertions(+), 222 deletions(-) create mode 100644 sf_mrs_connect/models/mrp_production.py create mode 100644 sf_plan/models/mrp_production.py diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 46c0ae39..2d0545af 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -1709,7 +1709,77 @@ class MrpProduction(models.Model): vals['procurement_group_id'] = product_group_id[product_id.id] else: vals['procurement_group_id'] = is_custemer_group_id[key] - return super(MrpProduction, self).create(vals_list) + + productions = super(MrpProduction, self).create(vals_list) + + # 查询成品工序排序(自动化产线加工),供后续使用 + product_model_type_routing_sorts = self.env['sf.product.model.type.routing.sort'].search([], order='sequence asc') + # 查询成品工序排序(人工线下加工),供后续使用 + manual_product_model_type_routing_sorts = self.env['sf.manual.product.model.type.routing.sort'].search([], order='sequence asc') + # 查询坯料工序排序,供后续使用 + embryo_model_type_routing_sorts = self.env['sf.embryo.model.type.routing.sort'].search([], order='sequence asc') + + for production in productions: + # 生成序列号 + production.action_generate_serial() + # 创建工序模板 + technology_design_values = [] + i = 0 + if production.product_id.categ_id.type == '成品': + # 根据加工面板的面数及成品工序模板生成工序设计 + if production.production_type == '自动化产线加工': + product_routing_workcenter = product_model_type_routing_sorts.filtered( + lambda s: s.product_model_type_id.id == production.product_id.product_model_type_id.id + ) + else: + product_routing_workcenter = manual_product_model_type_routing_sorts.filtered( + lambda s: s.manual_product_model_type_id.id == production.product_id.product_model_type_id.id + ) + if production.production_type == '自动化产线加工': + for k in (production.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) + ) + elif production.production_type == '人工线下加工': + for route in product_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str('ZM', 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.product_id.categ_id.type == '坯料': + embryo_routing_workcenter = embryo_model_type_routing_sorts.filtered( + lambda s: s.embryo_model_type_id.id == production.product_id.embryo_model_type_id.id + ) + 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) + ) + # 处理表面工艺 + 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: + if item.route_workcenter_id.surface_technics_id == process_param.process_id: + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str( + False, + item.route_workcenter_id, + i, + process_param + ) + ) + production.technology_design_ids = technology_design_values + # 设置制造订单状态为待工艺确认 + productions.write({'state': 'technology_to_confirmed'}) + return productions @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') diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 4134de94..7dbe1706 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -95,35 +95,36 @@ class StockRule(models.Model): precision_rounding=proc[ 0].product_uom.rounding) > 0) list2 = [] - for item in procurements: - num = int(item[0].product_qty) + for procurement, rule in procurements: + num = int(procurement.product_qty) - product = self.env['product.product'].search( - [("id", '=', item[0].product_id.id)]) - product_tmpl = self.env['product.template'].search( - ["&", ("id", '=', product.product_tmpl_id.id), ('single_manufacturing', "!=", False)]) - if product_tmpl: + warehouse_id = rule.warehouse_id + if not warehouse_id: + warehouse_id = rule.location_dest_id.warehouse_id + manu_rule = rule.route_id.rule_ids.filtered(lambda r: r.action == 'manufacture' and r.warehouse_id == warehouse_id) + + if procurement.product_id.product_tmpl_id.single_manufacturing and manu_rule: if num > 1: for no in range(1, num + 1): Procurement = namedtuple('Procurement', ['product_id', 'product_qty', 'product_uom', 'location_id', 'name', 'origin', 'company_id', 'values']) - s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom, - location_id=item[0].location_id, - name=item[0].name, - origin=item[0].origin, - company_id=item[0].company_id, - values=item[0].values, + s = Procurement(product_id=procurement.product_id, product_qty=1.0, product_uom=procurement.product_uom, + location_id=procurement.location_id, + name=procurement.name, + origin=procurement.origin, + company_id=procurement.company_id, + values=procurement.values, ) - item1 = list(item) - item1[0] = s + # item1 = list(item) + # item1[0] = s - list2.append(tuple(item1)) + list2.append((s, rule)) else: - list2.append(item) + list2.append((procurement, rule)) else: - list2.append(item) + list2.append((procurement, rule)) for procurement, rule in list2: procure_method = rule.procure_method @@ -162,215 +163,203 @@ class StockRule(models.Model): [('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1) attachment_info.write({'name': name}) - @api.model - def _run_manufacture(self, procurements): - productions_values_by_company = defaultdict(list) - errors = [] - for procurement, rule in procurements: - if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0: - # If procurement contains negative quantity, don't create a MO that would be for a negative value. - continue - bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values) + # @api.model + # def _run_manufacture(self, procurements): + # productions_values_by_company = defaultdict(list) + # errors = [] + # for procurement, rule in procurements: + # if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0: + # # If procurement contains negative quantity, don't create a MO that would be for a negative value. + # continue + # bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values) - productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom)) + # productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom)) - if errors: - raise ProcurementException(errors) + # if errors: + # raise ProcurementException(errors) - for company_id, productions_values in productions_values_by_company.items(): - # create the MO as SUPERUSER because the current user may not have the rights to do it - # (mto product launched by a sale for example) - '''创建制造订单''' - 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 + # for company_id, productions_values in productions_values_by_company.items(): + # # create the MO as SUPERUSER because the current user may not have the rights to do it + # # (mto product launched by a sale for example) + # '''创建制造订单''' + # productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( + # productions_values) - # self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) - # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) + # ''' + # 创建工单 + # ''' + # # productions._create_workorder() + # # + # # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) + # productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \ + # ( + # p.move_dest_ids.procure_method != 'make_to_order' and not + # p.move_raw_ids and not p.workorder_ids)).action_confirm() + # # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区 + # for production in productions: + # if production.picking_ids: + # product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id + # if product_type_id.name == '坯料': + # location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) + # if not location_id: + # logging.info(f'没有搜索到【坯料存货区】: {location_id}') + # break + # for picking_id in production.picking_ids: + # if picking_id.picking_type_id.name == '内部调拨': + # if picking_id.location_dest_id.product_type != product_type_id: + # picking_id.location_dest_id = location_id.id + # elif picking_id.picking_type_id.name == '生产发料': + # if picking_id.location_id.product_type != product_type_id: + # picking_id.location_id = location_id.id - ''' - 创建工单 - ''' - # productions._create_workorder() - # - # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) - productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \ - ( - p.move_dest_ids.procure_method != 'make_to_order' and not - p.move_raw_ids and not p.workorder_ids)).action_confirm() - # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区 - for production in productions: - if production.picking_ids: - product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id - if product_type_id.name == '坯料': - location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) - if not location_id: - logging.info(f'没有搜索到【坯料存货区】: {location_id}') - break - for picking_id in production.picking_ids: - if picking_id.picking_type_id.name == '内部调拨': - if picking_id.location_dest_id.product_type != product_type_id: - picking_id.location_dest_id = location_id.id - elif picking_id.picking_type_id.name == '生产发料': - if picking_id.location_id.product_type != product_type_id: - picking_id.location_id = location_id.id + # for production in productions: + # ''' + # 创建制造订单时生成序列号 + # ''' + # # production.action_generate_serial() + # origin_production = production.move_dest_ids and production.move_dest_ids[ + # 0].raw_material_production_id or False + # orderpoint = production.orderpoint_id + # if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual': + # production.message_post( + # body=_('This production order has been created from Replenishment Report.'), + # message_type='comment', + # subtype_xmlid='mail.mt_note') + # elif orderpoint: + # production.message_post_with_view( + # 'mail.message_origin_link', + # values={'self': production, 'origin': orderpoint}, + # subtype_id=self.env.ref('mail.mt_note').id) + # elif origin_production: + # production.message_post_with_view( + # 'mail.message_origin_link', + # values={'self': production, 'origin': origin_production}, + # subtype_id=self.env.ref('mail.mt_note').id) - for production in productions: - ''' - 创建制造订单时生成序列号 - ''' - production.action_generate_serial() - origin_production = production.move_dest_ids and production.move_dest_ids[ - 0].raw_material_production_id or False - orderpoint = production.orderpoint_id - if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual': - production.message_post( - body=_('This production order has been created from Replenishment Report.'), - message_type='comment', - subtype_xmlid='mail.mt_note') - elif orderpoint: - production.message_post_with_view( - 'mail.message_origin_link', - values={'self': production, 'origin': orderpoint}, - subtype_id=self.env.ref('mail.mt_note').id) - elif origin_production: - production.message_post_with_view( - 'mail.message_origin_link', - values={'self': production, 'origin': origin_production}, - subtype_id=self.env.ref('mail.mt_note').id) + # ''' + # 创建生产计划 + # ''' + # # 工单耗时 + # # workorder_duration = 0 + # # for workorder in production.workorder_ids: + # # workorder_duration += workorder.duration_expected - ''' - 创建生产计划 - ''' - # 工单耗时 - workorder_duration = 0 - for workorder in production.workorder_ids: - workorder_duration += workorder.duration_expected - - sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) - # 如果订单为空,则获取来源制造订单的销售单 - 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对应的生产订单名称列表 - product_id_to_production_names = {} - # 对于每个product_id,获取其所有生产订单的名称 - for product_id, all_production in grouped_product_ids.items(): - # 为同一个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: - # 同一个产品多个制造订单对应一个编程单和模型库 - # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 - if not production_item.programming_no and production_item.production_type in ['自动化产线加工', - '人工线下加工']: - 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': '编程中'}) - 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)) - elif production_item.production_type == '人工线下加工': - for route in product_routing_workcenter: - i += 1 - technology_design_values.append( - self.env['sf.technology.design'].json_technology_design_str('ZM', 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_parameters = production_item.product_id.model_process_parameters_ids.filtered( - lambda pm: pm.process_id.id == p.id) - for process_parameter in process_parameters: - 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 + # # sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) + # # # 如果订单为空,则获取来源制造订单的销售单 + # # 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对应的生产订单名称列表 + # product_id_to_production_names = {} + # # 对于每个product_id,获取其所有生产订单的名称 + # for product_id, all_production in grouped_product_ids.items(): + # # 为同一个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: + # # # 同一个产品多个制造订单对应一个编程单和模型库 + # # # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 + # # if not production_item.programming_no and production_item.production_type in ['自动化产线加工', + # # '人工线下加工']: + # # 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': '编程中'}) + # 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)) + # elif production_item.production_type == '人工线下加工': + # for route in product_routing_workcenter: + # i += 1 + # technology_design_values.append( + # self.env['sf.technology.design'].json_technology_design_str('ZM', 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_parameters = production_item.product_id.model_process_parameters_ids.filtered( + # lambda pm: pm.process_id.id == p.id) + # for process_parameter in process_parameters: + # 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): diff --git a/sf_mrs_connect/models/__init__.py b/sf_mrs_connect/models/__init__.py index f01fb4fe..6450f26f 100644 --- a/sf_mrs_connect/models/__init__.py +++ b/sf_mrs_connect/models/__init__.py @@ -1,4 +1,5 @@ from . import ftp_operate from . import res_config_setting from . import sync_common -from . import order_price \ No newline at end of file +from . import order_price +from . import mrp_production diff --git a/sf_mrs_connect/models/mrp_production.py b/sf_mrs_connect/models/mrp_production.py new file mode 100644 index 00000000..ac3f6640 --- /dev/null +++ b/sf_mrs_connect/models/mrp_production.py @@ -0,0 +1,44 @@ +from itertools import groupby +from odoo import models, api +from odoo.tools.misc import OrderedSet + + +class MrpProduction(models.Model): + _inherit = 'mrp.production' + + + @api.model_create_multi + def create(self, vals_list): + """ + 生成编程单 + """ + productions = super().create(vals_list) + # 定义变量存储编程单 + grouped_product_programming_no = {} + # 定义产品拼接成的制造订单名称 + grouped_product_production_name = {} + # 查出所有的制造订单,为了适配通过补货生成的制造订单 + all_productions = self.env['mrp.production'].search([('origin', '=', productions[0].origin)]) + # 将不同产品的制造订单进行分组 + grouped_product_productions = {k: list(g) for k, g in groupby(all_productions, key=lambda x: x.product_id.id)} + + for product_id, grouped_productions in grouped_product_productions.items(): + # 产品对应的编程单号 + if product_id not in grouped_product_programming_no: + # 使用列表推导式获取非空的programming_no + programming_nos = [p.programming_no for p in grouped_productions if p.programming_no if p.programming_no is not False] + if programming_nos: + grouped_product_programming_no[product_id] = programming_nos[0] + grouped_product_production_name[product_id] = ','.join(list(map(lambda p:p.name, grouped_productions))) + # 同一个产品的制造订单只请求一次CNC编程 + for production in productions: + if not production.programming_no and production.production_type in ['自动化产线加工','人工线下加工']: + if production.product_id.id not in grouped_product_programming_no: + production.fetchCNC(grouped_product_production_name[production.product_id.id]) + grouped_product_programming_no[production.product_id.id] = production.programming_no + else: + production.write({ + 'programming_no': grouped_product_programming_no[production.product_id.id], + 'programming_state': '编程中' + }) + return productions \ No newline at end of file diff --git a/sf_plan/models/__init__.py b/sf_plan/models/__init__.py index 57dea5da..0eba6ec9 100644 --- a/sf_plan/models/__init__.py +++ b/sf_plan/models/__init__.py @@ -3,3 +3,4 @@ from . import custom_plan from . import change_manufactuing +from . import mrp_production diff --git a/sf_plan/models/mrp_production.py b/sf_plan/models/mrp_production.py new file mode 100644 index 00000000..961d5f35 --- /dev/null +++ b/sf_plan/models/mrp_production.py @@ -0,0 +1,41 @@ +from odoo import models, api + + +class MrpProduction(models.Model): + _inherit = 'mrp.production' + + @api.model_create_multi + def create(self, vals_list): + """ + 创建生产计划 + """ + productions = super().create(vals_list) + + for production in productions: + # 工单耗时 + workorder_duration = 0 + for workorder in production.workorder_ids: + workorder_duration += workorder.duration_expected + + sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) + # 如果订单为空,则获取来源制造订单的销售单 + 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(production.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', + }) + return productions \ No newline at end of file From 71a0f39ec99c704aad82de18a248612b0f703fcf Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Wed, 18 Jun 2025 10:50:13 +0800 Subject: [PATCH 02/48] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A6=82=E6=9E=9C?= =?UTF-8?q?=E9=87=87=E8=B4=AD=E8=AE=A2=E5=8D=95=E7=B1=BB=E5=9E=8B=E4=B8=BA?= =?UTF-8?q?=E5=A7=94=E5=A4=96=E5=8A=A0=E5=B7=A5=EF=BC=8C=E5=88=99=E4=B8=8D?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BE=9B=E5=BA=94=E5=95=86=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E7=94=9F=E6=88=90=E8=A1=A5=E7=BB=99=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/__init__.py | 1 + sf_manufacturing/models/bom.py | 16 ++++++++++++++++ sf_manufacturing/models/stock.py | 14 ++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 sf_manufacturing/models/bom.py diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index 8af783e7..c8af2d82 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -18,4 +18,5 @@ from . import quick_easy_order from . import purchase_order from . import quality_check from . import purchase_request_line +from . import bom # from . import stock_warehouse_orderpoint \ No newline at end of file diff --git a/sf_manufacturing/models/bom.py b/sf_manufacturing/models/bom.py new file mode 100644 index 00000000..d0a35300 --- /dev/null +++ b/sf_manufacturing/models/bom.py @@ -0,0 +1,16 @@ +from odoo import models +from odoo.osv.expression import AND + + +class MrpBom(models.Model): + _inherit = 'mrp.bom' + + def _bom_subcontract_find(self, product, picking_type=None, company_id=False, bom_type='subcontract', subcontractor=False): + domain = self._bom_find_domain(product, picking_type=picking_type, company_id=company_id, bom_type=bom_type) + if self.env.context.get('stock_picking') == 'outsourcing': + return self.search(domain, order='sequence, product_id, id', limit=1) + if subcontractor: + domain = AND([domain, [('subcontractor_ids', 'parent_of', subcontractor.ids)]]) + return self.search(domain, order='sequence, product_id, id', limit=1) + else: + return self.env['mrp.bom'] diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 4134de94..05943e83 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -1216,6 +1216,20 @@ class ReStockMove(models.Model): res['lot_id'] = self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids[0].lot_id.id return res + def _get_subcontract_bom(self): + self.ensure_one() + purchase_type = getattr(self.picking_id.purchase_id, 'purchase_type', False) + if purchase_type: + self = self.with_context(stock_picking=purchase_type) + bom = self.env['mrp.bom'].sudo()._bom_subcontract_find( + self.product_id, + picking_type=self.picking_type_id, + company_id=self.company_id.id, + bom_type='subcontract', + subcontractor=self.picking_id.partner_id + ) + return bom + class ReStockQuant(models.Model): _inherit = 'stock.quant' From f38f60a6a83dcc1d6adc6c390fee803656cb4f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Fri, 20 Jun 2025 11:24:39 +0800 Subject: [PATCH 03/48] =?UTF-8?q?=E8=AE=A1=E5=88=92=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=98=9F=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/models/sf_production_demand_plan.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index 79fcb99b..e4a9ae0e 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -323,8 +323,15 @@ class SfProductionDemandPlan(models.Model): date_planned_start = datetime.combine(date_part, time_part) pro_plan_list.production_line_id = sf_production_line.id pro_plan_list.date_planned_start = date_planned_start - for pro_plan in pro_plan_list: - pro_plan.do_production_schedule() + batch_size = 100 + for i in range(0, len(pro_plan_list), batch_size): + current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S') + batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, i)) + pro_plans = pro_plan_list[i:i+batch_size] + pro_plans.with_context( + job_batch=batch + ).with_delay().do_production_schedule() + batch.enqueue() def button_action_print(self): return { From 788183e2393b2e5244abe9148fdef98250c39874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Fri, 20 Jun 2025 13:52:37 +0800 Subject: [PATCH 04/48] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/models/sf_production_demand_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index e4a9ae0e..cbd83c02 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -323,7 +323,7 @@ class SfProductionDemandPlan(models.Model): date_planned_start = datetime.combine(date_part, time_part) pro_plan_list.production_line_id = sf_production_line.id pro_plan_list.date_planned_start = date_planned_start - batch_size = 100 + batch_size = 28 for i in range(0, len(pro_plan_list), batch_size): current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S') batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, i)) From e7afc76753d2da50e9b5f77c79d111a0b1efafec Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Fri, 20 Jun 2025 14:01:56 +0800 Subject: [PATCH 05/48] =?UTF-8?q?=E5=87=BA=E5=8E=82=E6=A3=80=E9=AA=8C?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E6=A8=A1=E7=89=88=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/insepection_report_template.xml | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml index dbc28574..7cef66e9 100644 --- a/sf_quality/data/insepection_report_template.xml +++ b/sf_quality/data/insepection_report_template.xml @@ -202,13 +202,10 @@ --> - - - - -
- -
+ + + + @@ -334,13 +331,10 @@ --> - - - - -
- -
+ + + + From 5947b3dfe9bc2b6978cd47da69b16e142df2e5f8 Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Fri, 20 Jun 2025 14:26:29 +0800 Subject: [PATCH 06/48] =?UTF-8?q?=E5=9B=9E=E9=80=80=E5=87=BA=E5=8E=82?= =?UTF-8?q?=E6=A3=80=E9=AA=8C=E6=8A=A5=E5=91=8A=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_quality/data/insepection_report_template.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml index 7cef66e9..c68893fb 100644 --- a/sf_quality/data/insepection_report_template.xml +++ b/sf_quality/data/insepection_report_template.xml @@ -202,11 +202,11 @@ --> - + - +
@@ -331,11 +331,11 @@ --> - + - +
From b33c992b25c91bc85f436748739a2ebaf058f211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Fri, 20 Jun 2025 14:51:58 +0800 Subject: [PATCH 07/48] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E8=AE=A1=E5=88=92=EF=BC=8C=E4=B8=8B=E8=BE=BE=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_demand_plan_queue/__init__.py | 2 ++ jikimo_demand_plan_queue/__manifest__.py | 18 +++++++++++++++++ jikimo_demand_plan_queue/models/__init__.py | 2 ++ .../models/production_demand_plan.py | 20 +++++++++++++++++++ sf_demand_plan/__manifest__.py | 2 +- .../models/sf_production_demand_plan.py | 15 ++++++-------- 6 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 jikimo_demand_plan_queue/__init__.py create mode 100644 jikimo_demand_plan_queue/__manifest__.py create mode 100644 jikimo_demand_plan_queue/models/__init__.py create mode 100644 jikimo_demand_plan_queue/models/production_demand_plan.py diff --git a/jikimo_demand_plan_queue/__init__.py b/jikimo_demand_plan_queue/__init__.py new file mode 100644 index 00000000..a0fdc10f --- /dev/null +++ b/jikimo_demand_plan_queue/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/jikimo_demand_plan_queue/__manifest__.py b/jikimo_demand_plan_queue/__manifest__.py new file mode 100644 index 00000000..aa970328 --- /dev/null +++ b/jikimo_demand_plan_queue/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +{ + 'name': '机企猫 需求计划排程队列', + 'version': '1.0', + 'summary': """ 使用队列进行排程 """, + 'author': 'fox', + 'website': '', + 'category': '', + 'depends': ['queue_job', 'sf_demand_plan'], + 'data': [ + + ], + + 'application': True, + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/jikimo_demand_plan_queue/models/__init__.py b/jikimo_demand_plan_queue/models/__init__.py new file mode 100644 index 00000000..54314ee3 --- /dev/null +++ b/jikimo_demand_plan_queue/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import production_demand_plan diff --git a/jikimo_demand_plan_queue/models/production_demand_plan.py b/jikimo_demand_plan_queue/models/production_demand_plan.py new file mode 100644 index 00000000..fd51e055 --- /dev/null +++ b/jikimo_demand_plan_queue/models/production_demand_plan.py @@ -0,0 +1,20 @@ +from odoo import models, fields + + +class ProductionDemandPlan(models.Model): + _inherit = 'sf.production.demand.plan' + + + def _do_production_schedule(self, pro_plan_list): + """使用队列进行排程""" + batch_size = 10 + current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S') + index = 1 + for i in range(0, len(pro_plan_list), batch_size): + batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, index)) + pro_plans = pro_plan_list[i:i+batch_size] + pro_plans.with_context( + job_batch=batch + ).with_delay().do_production_schedule() + index += 1 + batch.enqueue() \ No newline at end of file diff --git a/sf_demand_plan/__manifest__.py b/sf_demand_plan/__manifest__.py index 2a543f1a..aab4ed99 100644 --- a/sf_demand_plan/__manifest__.py +++ b/sf_demand_plan/__manifest__.py @@ -10,7 +10,7 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['sf_plan', 'jikimo_printing'], + 'depends': ['sf_plan'], 'data': [ 'security/ir.model.access.csv', 'views/demand_plan.xml', diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index cbd83c02..12214bff 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -323,15 +323,12 @@ class SfProductionDemandPlan(models.Model): date_planned_start = datetime.combine(date_part, time_part) pro_plan_list.production_line_id = sf_production_line.id pro_plan_list.date_planned_start = date_planned_start - batch_size = 28 - for i in range(0, len(pro_plan_list), batch_size): - current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S') - batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, i)) - pro_plans = pro_plan_list[i:i+batch_size] - pro_plans.with_context( - job_batch=batch - ).with_delay().do_production_schedule() - batch.enqueue() + self._do_production_schedule(pro_plan_list) + + def _do_production_schedule(self, pro_plan_list): + for pro_plan in pro_plan_list: + pro_plan.do_production_schedule() + def button_action_print(self): return { From 5bb6fcd4f7b70fb38986971a47121640c1c47c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Fri, 20 Jun 2025 15:03:42 +0800 Subject: [PATCH 08/48] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_demand_plan_queue/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jikimo_demand_plan_queue/__manifest__.py b/jikimo_demand_plan_queue/__manifest__.py index aa970328..83f5e07d 100644 --- a/jikimo_demand_plan_queue/__manifest__.py +++ b/jikimo_demand_plan_queue/__manifest__.py @@ -6,7 +6,7 @@ 'author': 'fox', 'website': '', 'category': '', - 'depends': ['queue_job', 'sf_demand_plan'], + 'depends': ['queue_job_batch', 'sf_demand_plan'], 'data': [ ], From 2e168a4ba71c33d410f4e3e2b59dab2410aae53c Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Fri, 20 Jun 2025 15:35:07 +0800 Subject: [PATCH 09/48] =?UTF-8?q?=E5=87=BA=E5=8E=82=E6=A3=80=E9=AA=8C?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E6=A8=A1=E7=89=88-=E9=A1=B5=E8=84=9A?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/insepection_report_template.xml | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml index c68893fb..4b91ec13 100644 --- a/sf_quality/data/insepection_report_template.xml +++ b/sf_quality/data/insepection_report_template.xml @@ -201,12 +201,31 @@ --> + - + - + - From 13d33488dc21aafedb6771c568c84002e498a1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Fri, 20 Jun 2025 15:39:11 +0800 Subject: [PATCH 10/48] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E9=87=8F=E5=AD=97=E6=AE=B5=E4=B8=BA=C2=96qty=5Fproduction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_machine_connect/controllers/controllers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sf_machine_connect/controllers/controllers.py b/sf_machine_connect/controllers/controllers.py index 78202ea6..4974127e 100644 --- a/sf_machine_connect/controllers/controllers.py +++ b/sf_machine_connect/controllers/controllers.py @@ -437,7 +437,7 @@ class Sf_Dashboard_Connect(http.Controller): ('state', 'in', ['ready', 'progress', 'done']) ]) - plan_data_total_counts = sum(plan_data_total.mapped('qty_produced')) + plan_data_total_counts = sum(plan_data_total.mapped('qty_production')) # # 工单完成量 # plan_data_finish_counts = plan_obj.search_count( @@ -717,7 +717,7 @@ class Sf_Dashboard_Connect(http.Controller): # 使用小时和分钟作为键,确保每个小时的数据有独立的键 key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键 # time_count_dict[key] = len(orders) - plan_count_dict[key] = sum(interval_orders.mapped('qty_produced')) + plan_count_dict[key] = sum(interval_orders.mapped('qty_production')) # order_counts.append() res['data'][line] = { From e3fb2668906fcee98714c14456cf0ac3aa6a5e89 Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Fri, 20 Jun 2025 15:55:58 +0800 Subject: [PATCH 11/48] =?UTF-8?q?=E5=87=BA=E5=8E=82=E6=A3=80=E9=AA=8C?= =?UTF-8?q?=E6=8A=A5=E5=91=8A-=E9=A1=B5=E8=84=9A=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/insepection_report_template.xml | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml index 4b91ec13..5b18d641 100644 --- a/sf_quality/data/insepection_report_template.xml +++ b/sf_quality/data/insepection_report_template.xml @@ -202,20 +202,6 @@ -->