266 lines
12 KiB
Python
266 lines
12 KiB
Python
# -*- 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
|