diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index 4c7f7ae0..f5627305 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -7,7 +7,7 @@ from odoo.tools import float_compare from datetime import datetime, timedelta from odoo.exceptions import UserError import re - +#本地环境问题 不加报错 class ProductCategory(models.Model): _inherit = 'product.category' @@ -709,190 +709,252 @@ class SfProductionDemandPlan(models.Model): }} self.action_confirm() -def action_confirm(self): - self = self.with_context( - demand_plan_line_id=self.id - ) - self.mrp_bom_create() - self._action_launch_stock_rule() - if self.supply_method in ('automation', 'manual'): - self.write({'status': '50'}) - self.update_sale_order_state() - else: - self.write({'status': '60'}) - self.update_sale_order_state() + def action_confirm(self): + """ + 确认需求计划行,创建 BOM、触发库存规则,并更新状态。 + """ + # 将当前需求计划行 ID 写入上下文,便于后续方法使用 + self = self.with_context(demand_plan_line_id=self.id) -def _get_embryo_template_by_supply_method(self): - """ - 根据供货方式获取对应的胚料模板,供mrp_bom_create调用。 - """ - supply_map = { - 'automation': self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo(), - 'outsourcing': self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo(), - 'purchase': self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo(), - 'manual': self.env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo(), - 'material_customer_provided': self.env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo(), - } - template = supply_map.get(self.supply_method) - if not template: - raise UserError(f"未配置供货方式 {self.supply_method} 对应的胚料模板") - return template + # 创建物料清单(BOM),根据供货方式进行不同的处理 + self.mrp_bom_create() -def mrp_bom_create(self): - if self.supply_method in ('automation', 'manual'): - line_ids = self.demand_plan_id.line_ids.filtered( - lambda p: p.supply_method in ('automation', 'manual') and p.status in ('50', '60')) - if line_ids: - self.bom_id = line_ids[0].bom_id.id - return - elif self.supply_method == 'outsourcing': - line_ids = self.demand_plan_id.line_ids.filtered( - lambda p: p.supply_method == 'outsourcing' and p.status == '60') - if line_ids: - self.bom_id = line_ids[0].bom_id.id - return + # 启动库存规则(创建采购、生产等) + self._action_launch_stock_rule() - bom_type = '' - if self.supply_method == 'automation': - bom_type = 'normal' - product_template_id = self.env.ref('sf_dlm.product_template_sf').sudo().product_tmpl_id - elif self.supply_method == 'outsourcing': - bom_type = 'subcontract' - product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_outsourcing').sudo() - elif self.supply_method == 'purchase': - product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_purchase').sudo() - elif self.supply_method == 'manual': - bom_type = 'normal' - product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo() - - self.product_id.product_tmpl_id.copy_template(product_template_id) - future_time = datetime.now() + timedelta(hours=8) - code = f"{self.product_id.default_code}-{bom_type}-{future_time.strftime('%Y%m%d%H%M%S')}" - - order_id = self.sale_order_id - product = self.product_id - 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, - 'blank_volume': product.model_volume, - 'blank_area': product.model_area, - 'price': product.list_price, - 'embryo_redundancy_id': self.sale_order_line_id.embryo_redundancy_id, - 'model_id': self.model_id - } - product_name = '' - match = re.search(r'(S\d{5}-\d+)', product.name) - product_seria = 0 - if match: - product_name = match.group(0) - product_seria = int(product_name.split('-')[-1]) - - if self.supply_method != 'purchase': - if self.sale_order_line_id.embryo_redundancy_id: - embryo_template = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo() - embryo_key = 'material_customer_provided' + # 根据供货方式设置状态字段 + if self.supply_method in ('automation', 'manual'): + self.write({'status': '50'}) # 自动/手工 供货:待排产 + self.update_sale_order_state() else: - embryo_template = self._get_embryo_template_by_supply_method() - embryo_key = self.supply_method + self.write({'status': '60'}) # 外购/外协/客户自供:无需排产 + self.update_sale_order_state() - tracking_method = embryo_template.tracking - embryo_product = self.env['product.template'].sudo().no_bom_product_create( - embryo_template.with_context(active_test=False).product_variant_id, - item, - order_id, - embryo_key, - product_seria, - product - ) + def _get_embryo_template_by_supply_method(self): + """ + 根据供货方式返回对应的胚料模板 product.template 记录。 + """ + supply_map = { + 'automation': self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo(), + 'outsourcing': self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo(), + 'purchase': self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo(), + 'manual': self.env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo(), + 'material_customer_provided': self.env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo(), + } + template = supply_map.get(self.supply_method) + if not template: + raise UserError(f"未配置供货方式 {self.supply_method} 对应的胚料模板") + return template - if embryo_product == -3: - raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配') - if embryo_key in ('automation', 'manual', 'material_customer_provided'): - embryo_bom_type = 'normal' - elif embryo_key == 'outsourcing': - embryo_bom_type = 'subcontract' - elif embryo_key == 'purchase': - embryo_bom_type = 'purchase' + def mrp_bom_create(self): + """ + 创建 BOM(包含胚料与成品 BOM),用于后续生产或采购流程。 + """ + # 如果同一计划中已有对应的 BOM 可复用,则直接使用 + if self.supply_method in ('automation', 'manual'): + line_ids = self.demand_plan_id.line_ids.filtered( + lambda p: p.supply_method in ('automation', 'manual') and p.status in ('50', '60')) + if line_ids: + self.bom_id = line_ids[0].bom_id.id + return + elif self.supply_method == 'outsourcing': + line_ids = self.demand_plan_id.line_ids.filtered( + lambda p: p.supply_method == 'outsourcing' and p.status == '60') + if line_ids: + self.bom_id = line_ids[0].bom_id.id + return + + # 根据供货方式选择模板和 BOM 类型 + bom_type = '' + if self.supply_method == 'automation': + bom_type = 'normal' + product_template_id = self.env.ref('sf_dlm.product_template_sf').sudo().product_tmpl_id + elif self.supply_method == 'outsourcing': + bom_type = 'subcontract' + product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_outsourcing').sudo() + elif self.supply_method == 'purchase': + product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_purchase').sudo() + elif self.supply_method == 'manual': + bom_type = 'normal' + product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo() + + # 使用模板复制内容到当前产品 + self.product_id.product_tmpl_id.copy_template(product_template_id) + + # 构造 BOM 编码(包含时间戳) + future_time = datetime.now() + timedelta(hours=8) + code = f"{self.product_id.default_code}-{bom_type}-{future_time.strftime('%Y%m%d%H%M%S')}" + + order_id = self.sale_order_id + product = self.product_id + + # 构造胚料产品的参数 + 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, + 'blank_volume': product.model_volume, + 'blank_area': product.model_area, + 'price': product.list_price, + 'embryo_redundancy_id': self.sale_order_line_id.embryo_redundancy_id, + 'model_id': self.model_id + } + + # 从产品名中提取编号(如 S12345-3) + product_name = '' + match = re.search(r'(S\d{5}-\d+)', product.name) + product_seria = 0 + if match: + product_name = match.group(0) + product_seria = int(product_name.split('-')[-1]) + + # 如果供货方式不是采购,则需要先创建胚料产品 + if self.supply_method != 'purchase': + # 判断是否为客户自供 + if self.sale_order_line_id.embryo_redundancy_id: + embryo_template = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo() + embryo_key = 'material_customer_provided' + else: + embryo_template = self._get_embryo_template_by_supply_method() + embryo_key = self.supply_method + + # 获取批次追踪方式 + tracking_method = embryo_template.tracking + + # 创建胚料产品(无 BOM 产品) + embryo_product = self.env['product.template'].sudo().no_bom_product_create( + embryo_template.with_context(active_test=False).product_variant_id, + item, + order_id, + embryo_key, + product_seria, + product + ) + + if embryo_product == -3: + raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配') + + # 设置胚料 BOM 类型 + if embryo_key in ('automation', 'manual', 'material_customer_provided'): + embryo_bom_type = 'normal' + elif embryo_key == 'outsourcing': + embryo_bom_type = 'subcontract' + elif embryo_key == 'purchase': + embryo_bom_type = 'purchase' + else: + embryo_bom_type = 'normal' + + # 创建胚料 BOM 及 BOM 行 + embryo_bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create( + embryo_product, embryo_bom_type, True, tracking=tracking_method) + + embryo_bom_line = embryo_bom.with_user(self.env.ref("base.user_admin")).bom_create_line(embryo_product) + if not embryo_bom_line: + raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配') + + # 创建成品 BOM(包含胚料) + product_bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create( + product, bom_type, 'product', code, tracking=tracking_method) + product_bom.with_user(self.env.ref("base.user_admin")).bom_create_line_has(embryo_product) + + # 赋值 BOM ID + self.bom_id = product_bom.id + + + def _action_launch_stock_rule(self): + """ + 触发库存规则(如采购、生产),并确认相关拣货单。 + """ + procurements = [] + + group_id = self.sale_order_id.procurement_group_id + if not group_id: + # 没有分组则创建 + group_id = self.env['procurement.group'].create(self._prepare_procurement_group_vals()) + self.sale_order_id.procurement_group_id = group_id else: - embryo_bom_type = 'normal' + # 若已有分组但字段有变动则更新 + updated_vals = {} + if group_id.partner_id != self.sale_order_id.partner_shipping_id: + updated_vals.update({'partner_id': self.sale_order_id.partner_shipping_id.id}) + if group_id.move_type != self.sale_order_id.picking_policy: + updated_vals.update({'move_type': self.sale_order_id.picking_policy}) + if updated_vals: + group_id.write(updated_vals) - embryo_bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create( - embryo_product, embryo_bom_type, True, tracking=tracking_method) + # 构造 procurement 所需的字段 + values = self._prepare_procurement_values(group_id=group_id) - embryo_bom_line = embryo_bom.with_user(self.env.ref("base.user_admin")).bom_create_line(embryo_product) - if not embryo_bom_line: - raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配') + # 单位换算 + line_uom = self.sale_order_line_id.product_uom + quant_uom = self.product_id.uom_id + plan_uom_qty, procurement_uom = line_uom._adjust_uom_quantities(self.plan_uom_qty, quant_uom) - product_bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create( - product, bom_type, 'product', code, tracking=tracking_method) - product_bom.with_user(self.env.ref("base.user_admin")).bom_create_line_has(embryo_product) - self.bom_id = product_bom.id + # 创建 procurement 请求 + procurements.append(self.env['procurement.group'].Procurement( + self.product_id, plan_uom_qty, procurement_uom, + self.sale_order_id.partner_shipping_id.property_stock_customer, + self.product_id.display_name, self.sale_order_id.name, self.sale_order_id.company_id, values)) -def _action_launch_stock_rule(self): - procurements = [] - group_id = self.sale_order_id.procurement_group_id - if not group_id: - group_id = self.env['procurement.group'].create(self._prepare_procurement_group_vals()) - self.sale_order_id.procurement_group_id = group_id - else: - updated_vals = {} - if group_id.partner_id != self.sale_order_id.partner_shipping_id: - updated_vals.update({'partner_id': self.sale_order_id.partner_shipping_id.id}) - if group_id.move_type != self.sale_order_id.picking_policy: - updated_vals.update({'move_type': self.sale_order_id.picking_policy}) - if updated_vals: - group_id.write(updated_vals) - values = self._prepare_procurement_values(group_id=group_id) - line_uom = self.sale_order_line_id.product_uom - quant_uom = self.product_id.uom_id - plan_uom_qty, procurement_uom = line_uom._adjust_uom_quantities(self.plan_uom_qty, quant_uom) - procurements.append(self.env['procurement.group'].Procurement( - self.product_id, plan_uom_qty, procurement_uom, - self.sale_order_id.partner_shipping_id.property_stock_customer, - self.product_id.display_name, self.sale_order_id.name, self.sale_order_id.company_id, values)) - if procurements: - procurement_group = self.env['procurement.group'] - if self.env.context.get('import_file'): - procurement_group = procurement_group.with_context(import_file=False) - procurement_group.run(procurements) - orders = self.mapped('sale_order_id') - for order in orders: - pickings_to_confirm = order.picking_ids.filtered(lambda p: p.state not in ['cancel', 'done']) - if pickings_to_confirm: - pickings_to_confirm.action_confirm() - return True + # 执行调度 + if procurements: + procurement_group = self.env['procurement.group'] + if self.env.context.get('import_file'): + procurement_group = procurement_group.with_context(import_file=False) + procurement_group.run(procurements) -def _prepare_procurement_group_vals(self): - return { - 'name': self.sale_order_id.name, - 'move_type': self.sale_order_id.picking_policy, - 'sale_id': self.sale_order_id.id, - 'partner_id': self.sale_order_id.partner_shipping_id.id, - } + # 确认相关的拣货单 + orders = self.mapped('sale_order_id') + for order in orders: + pickings_to_confirm = order.picking_ids.filtered(lambda p: p.state not in ['cancel', 'done']) + if pickings_to_confirm: + pickings_to_confirm.action_confirm() + + return True + + + def _prepare_procurement_group_vals(self): + """ + 构造创建 procurement group 所需的字段。 + """ + return { + 'name': self.sale_order_id.name, + 'move_type': self.sale_order_id.picking_policy, + 'sale_id': self.sale_order_id.id, + 'partner_id': self.sale_order_id.partner_shipping_id.id, + } + + + def _prepare_procurement_values(self, group_id=False): + """ + 构造单个 procurement 请求所需的字段字典。 + """ + self.ensure_one() + + # 交货日期与计划日期 + date_deadline = self.sale_order_id.commitment_date or ( + self.sale_order_id.date_order + timedelta(days=self.sale_order_line_id.customer_lead or 0.0)) + date_planned = date_deadline - timedelta(days=self.sale_order_id.company_id.security_lead) + + values = { + 'group_id': group_id, + 'sale_line_id': self.sale_order_line_id.id, + 'date_planned': date_planned, + 'date_deadline': date_deadline, + 'route_ids': self.route_ids, + 'warehouse_id': self.sale_order_id.warehouse_id or False, + 'partner_id': self.sale_order_id.partner_shipping_id.id, + 'product_description_variants': self.sale_order_line_id.with_context( + lang=self.sale_order_id.partner_id.lang)._get_sale_order_line_multiline_description_variants(), + 'company_id': self.sale_order_id.company_id, + 'product_packaging_id': self.sale_order_line_id.product_packaging_id, + 'sequence': self.sale_order_line_id.sequence, + 'demand_plan_line_id': self.id + } + + return values -def _prepare_procurement_values(self, group_id=False): - self.ensure_one() - date_deadline = self.sale_order_id.commitment_date or ( - self.sale_order_id.date_order + timedelta(days=self.sale_order_line_id.customer_lead or 0.0)) - date_planned = date_deadline - timedelta(days=self.sale_order_id.company_id.security_lead) - values = { - 'group_id': group_id, - 'sale_line_id': self.sale_order_line_id.id, - 'date_planned': date_planned, - 'date_deadline': date_deadline, - 'route_ids': self.route_ids, - 'warehouse_id': self.sale_order_id.warehouse_id or False, - 'partner_id': self.sale_order_id.partner_shipping_id.id, - 'product_description_variants': self.sale_order_line_id.with_context( - lang=self.sale_order_id.partner_id.lang)._get_sale_order_line_multiline_description_variants(), - 'company_id': self.sale_order_id.company_id, - 'product_packaging_id': self.sale_order_line_id.product_packaging_id, - 'sequence': self.sale_order_line_id.sequence, - 'demand_plan_line_id': self.id - } - return values