# -*- coding: utf-8 -*- from odoo import models, fields, api, _ from odoo.tools import float_compare from odoo.exceptions import ValidationError import re class SfDemandPlan(models.Model): _name = 'sf.demand.plan' _description = 'sf_demand_plan' state = fields.Selection([ ('10', '待工艺设计'), ('30', '部分下达'), ('40', '已下达'), ('50', '取消'), ], string='状态', default='10', compute='_compute_state', store=True) line_ids = fields.One2many(comodel_name='sf.production.demand.plan', inverse_name='demand_plan_id', string="需求计划", copy=True) sale_order_id = fields.Many2one(comodel_name="sale.order", string="销售订单", readonly=True) sale_order_line_id = fields.Many2one(comodel_name="sale.order.line", string="销售订单明细", readonly=True) product_id = fields.Many2one( comodel_name='product.product', related='sale_order_line_id.product_id', string='产品', store=True, index=True) part_name = fields.Char('零件名称', related='product_id.part_name') part_number = fields.Char('零件图号', compute='_compute_part_number', store=True) materials_id = fields.Char('材料', compute='_compute_materials_id', store=True) blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类', related='product_id.blank_type') blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型', related='product_id.blank_precision') manual_quotation = fields.Boolean('人工编程', related='product_id.manual_quotation', default=False) embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long', store=True) is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True) pending_qty = fields.Float( string="待计划", compute='_compute_pending_qty', store=True) planned_qty = fields.Float( string="已计划", compute='_compute_planned_qty', store=True) model_id = fields.Char('模型ID', related='product_id.model_id') customer_name = fields.Char('客户', related='sale_order_id.customer_name') product_uom_qty = fields.Float( string="需求数量", related='sale_order_line_id.product_uom_qty', store=True) deadline_of_delivery = fields.Date('客户交期', related='sale_order_line_id.delivery_end_date', store=True) contract_date = fields.Date('合同日期', related='sale_order_id.contract_date') contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True) model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'demand_plan_process_parameter_rel', string='表面工艺', compute='_compute_model_process_parameters_ids' , store=True ) model_machining_precision = fields.Selection(related='product_id.model_machining_precision', string='精度') inventory_quantity_auto_apply = fields.Float( string="成品库存", compute='_compute_inventory_quantity_auto_apply' ) priority = fields.Selection([ ('1', '紧急'), ('2', '高'), ('3', '中'), ('4', '低'), ], string='优先级', default='3') overdelivery_allowed = fields.Boolean('可超量发货', default=False) hide_button_release_plan = fields.Boolean( string='显示下达计划按钮', compute='_compute_hide_button_release_plan', default=False ) readonly_custom_made_type = fields.Boolean( string='字段自制类型只读', compute='_compute_readonly_custom_made_type', default=False ) demand_plan_number = fields.Char('需求计划号', compute='_compute_demand_plan_number', readonly=True, store=True) origin = fields.Char('来源', related='sale_order_id.name', readonly=True, store=True) @api.depends('product_id.part_number', 'product_id.model_name') def _compute_part_number(self): for line in self: if line.product_id: if line.product_id.part_number: line.part_number = line.product_id.part_number else: if line.product_id.model_name: line.part_number = line.product_id.model_name.rsplit('.', 1)[0] else: line.part_number = None @api.depends('product_id.materials_id') def _compute_materials_id(self): for line in self: if line.product_id: line.materials_id = f"{line.product_id.materials_id.name}/{line.product_id.materials_type_id.name}" else: line.materials_id = None @api.depends('product_id.model_long', 'product_id.model_width', 'product_id.model_height') def _compute_embryo_long(self): for line in self: if line.product_id: if line.product_id.blank_type == '圆料': line.embryo_long = f"Ø{round(line.product_id.model_width, 3)}*{round(line.product_id.model_long, 3)}" else: line.embryo_long = f"{round(line.product_id.model_long, 3)}*{round(line.product_id.model_width, 3)}*{round(line.product_id.model_height, 3)}" else: line.embryo_long = None @api.depends('product_id.model_process_parameters_ids') def _compute_model_process_parameters_ids(self): for line in self: if line.product_id and line.product_id.model_process_parameters_ids: line.model_process_parameters_ids = [(6, 0, line.product_id.model_process_parameters_ids.ids)] else: line.model_process_parameters_ids = [(5, 0, 0)] def _compute_inventory_quantity_auto_apply(self): location_id = self.env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id product_ids = self.mapped('product_id').ids if product_ids: quant_data = self.env['stock.quant'].read_group( domain=[ ('product_id', 'in', product_ids), ('location_id', '=', location_id) ], fields=['product_id', 'inventory_quantity_auto_apply'], groupby=['product_id'] ) quantity_map = {item['product_id'][0]: item['inventory_quantity_auto_apply'] for item in quant_data} else: quantity_map = {} for line in self: if line.product_id: line.inventory_quantity_auto_apply = quantity_map.get(line.product_id.id, 0.0) else: line.inventory_quantity_auto_apply = 0.0 @api.depends('product_uom_qty', 'line_ids.plan_uom_qty') def _compute_pending_qty(self): for line in self: sum_plan_uom_qty = sum(line.line_ids.mapped('plan_uom_qty')) pending_qty = line.product_uom_qty - sum_plan_uom_qty if float_compare(pending_qty, 0, precision_rounding=line.product_id.uom_id.rounding) == -1: line.pending_qty = 0 else: line.pending_qty = pending_qty @api.depends('line_ids.plan_uom_qty') def _compute_planned_qty(self): for line in self: line.planned_qty = sum(line.line_ids.mapped('plan_uom_qty')) @api.depends('line_ids.status') def _compute_hide_button_release_plan(self): for line in self: line.hide_button_release_plan = bool(line.line_ids.filtered( lambda p: p.status == '30')) @api.depends('line_ids.status', 'sale_order_id.state') def _compute_state(self): for line in self: status_line = line.line_ids.filtered(lambda p: p.status == '60') if not line.line_ids: line.state = '10' elif line.sale_order_id.state == 'cancel': line.state = '50' line.line_ids.status = '100' elif len(line.line_ids) == len(status_line): line.state = '40' elif bool(status_line): line.state = '30' else: line.state = '10' @api.depends('line_ids.status') def _compute_readonly_custom_made_type(self): for line in self: production_demand_plan = line.line_ids.filtered( lambda p: p.status in ('50', '60') and p.new_supply_method == 'custom_made') line.readonly_custom_made_type = bool(production_demand_plan) @api.constrains('line_ids') def check_line_ids(self): for item in self: if not item.line_ids: raise ValidationError('计划不能为空!') def write(self, vals): res = super(SfDemandPlan, self).write(vals) if 'line_ids' in vals: for line in self.line_ids: if not line.sale_order_id: line.sale_order_id = self.sale_order_id if not line.sale_order_line_id: line.sale_order_line_id = self.sale_order_line_id return res def name_get(self): result = [] for plan in self: result.append((plan.id, plan.demand_plan_number)) return result def button_production_release_plan(self): line_ids = self.line_ids.filtered(lambda p: p.status == '30') sum_product_uom_qty = sum(line_ids.mapped('plan_uom_qty')) customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers') check_overdelivery_allowed = False for line in line_ids: if line.location_id.id == customer_location_id: if not self.overdelivery_allowed: if float_compare(sum_product_uom_qty, self.product_uom_qty, precision_rounding=line.product_id.uom_id.rounding) == 1: check_overdelivery_allowed = True if check_overdelivery_allowed: raise ValidationError(f"已禁止向合作伙伴/客户超量发货,请更换“补货原因”或将“可超量发货”设置为“是”。") elif float_compare(sum_product_uom_qty, self.product_uom_qty, precision_rounding=self.product_id.uom_id.rounding) == 1: return { 'name': _('需求计划'), 'type': 'ir.actions.act_window', 'views': [(self.env.ref( 'sf_demand_plan.sf_release_plan_wizard_form').id, 'form')], 'res_model': 'sf.release.plan.wizard', 'target': 'new', 'context': { 'default_demand_plan_line_id': line_ids.ids, 'default_release_message': f"您正在下达计划量 {sum_product_uom_qty},需求数量为 {self.product_uom_qty},已超过需求数量,是否继续?", }} else: for demand_plan_line_id in line_ids: demand_plan_line_id.action_confirm() # 需求要求取值格式是来源+来源明细行ID,但是来源明细行ID取得就是product_id.name得最后一位,所以这里也直接截取product_id.name @api.depends('product_id.name') def _compute_demand_plan_number(self): for line in self: product_name = line.product_id.name or '' plan_no = None if line.product_id: # 使用正则表达式匹配P-后面的所有字符 match = re.search(r'P-(.*)', product_name) if match: plan_no = match.group(1) line.demand_plan_number = plan_no else: line.demand_plan_number = None