From e1644c3aa404523e4043c191f9582d5faa35636e Mon Sep 17 00:00:00 2001 From: chenye Date: Wed, 2 Jul 2025 16:19:24 +0800 Subject: [PATCH] =?UTF-8?q?=E7=89=A9=E6=96=99=E9=9C=80=E6=B1=82=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E7=AE=A1=E7=90=86=E8=AF=A6=E6=83=85=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E7=BB=83=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/__manifest__.py | 5 +- sf_demand_plan/migrations/1.1/post-migrate.py | 25 + sf_demand_plan/models/__init__.py | 8 +- sf_demand_plan/models/mrp_bom.py | 20 + sf_demand_plan/models/mrp_production.py | 26 + sf_demand_plan/models/purchase_order.py | 22 + sf_demand_plan/models/purchase_request.py | 110 +++ sf_demand_plan/models/sale_order.py | 64 +- sf_demand_plan/models/sf_demand_main_plan.py | 206 ++++ .../models/sf_production_demand_plan.py | 923 +++++++++++------- sf_demand_plan/models/stock_route.py | 49 + sf_demand_plan/models/stock_rule.py | 22 + sf_demand_plan/security/ir.model.access.csv | 11 +- sf_demand_plan/views/demand_main_plan.xml | 70 ++ sf_demand_plan/views/demand_plan.xml | 64 +- sf_demand_plan/views/sale_order_views.xml | 29 + sf_demand_plan/wizard/__init__.py | 1 + .../wizard/sf_release_plan_wizard.py | 22 + .../wizard/sf_release_plan_wizard_views.xml | 22 + 19 files changed, 1325 insertions(+), 374 deletions(-) create mode 100644 sf_demand_plan/migrations/1.1/post-migrate.py create mode 100644 sf_demand_plan/models/mrp_bom.py create mode 100644 sf_demand_plan/models/mrp_production.py create mode 100644 sf_demand_plan/models/purchase_order.py create mode 100644 sf_demand_plan/models/purchase_request.py create mode 100644 sf_demand_plan/models/sf_demand_main_plan.py create mode 100644 sf_demand_plan/models/stock_route.py create mode 100644 sf_demand_plan/models/stock_rule.py create mode 100644 sf_demand_plan/views/demand_main_plan.xml create mode 100644 sf_demand_plan/views/sale_order_views.xml create mode 100644 sf_demand_plan/wizard/sf_release_plan_wizard.py create mode 100644 sf_demand_plan/wizard/sf_release_plan_wizard_views.xml diff --git a/sf_demand_plan/__manifest__.py b/sf_demand_plan/__manifest__.py index e65fdca7..0e9b3ca5 100644 --- a/sf_demand_plan/__manifest__.py +++ b/sf_demand_plan/__manifest__.py @@ -10,11 +10,14 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['sf_plan','jikimo_printing'], + 'depends': ['sf_plan'], 'data': [ 'security/ir.model.access.csv', + 'views/sale_order_views.xml', 'views/demand_plan.xml', + 'views/demand_main_plan.xml', 'wizard/sf_demand_plan_print_wizard_view.xml', + 'wizard/sf_release_plan_wizard_views.xml', ], 'demo': [ ], diff --git a/sf_demand_plan/migrations/1.1/post-migrate.py b/sf_demand_plan/migrations/1.1/post-migrate.py new file mode 100644 index 00000000..950bcb88 --- /dev/null +++ b/sf_demand_plan/migrations/1.1/post-migrate.py @@ -0,0 +1,25 @@ +# migrations/1.1.0/post-migrate.py +import os +import csv +import logging +from odoo import api, SUPERUSER_ID + +_logger = logging.getLogger(__name__) + +#需求计划模块升级的时候,同步处理存量需求明细数据,关联上新加的需求计划主表 +def migrate(cr, version): + # 获取环境 + env = api.Environment(cr, SUPERUSER_ID, {}) + + ProductionLine = env['sf.production.demand.plan'] + DemandPlan = env['sf.demand.main.plan'] + + lines = ProductionLine.search([('demand_plan_id', '=', False)]) + for line in lines: + vals = { + 'sale_order_id': line.sale_order_id.id, + 'sale_order_line_id': line.sale_order_line_id.id, + 'line_ids': line.ids + } + new_plan = DemandPlan.create(vals) + line.write({'demand_plan_id': new_plan.id}) diff --git a/sf_demand_plan/models/__init__.py b/sf_demand_plan/models/__init__.py index a0554c11..0f8324dc 100644 --- a/sf_demand_plan/models/__init__.py +++ b/sf_demand_plan/models/__init__.py @@ -1,4 +1,10 @@ # -*- coding: utf-8 -*- - +from . import mrp_bom +from . import mrp_production +from . import sf_demand_main_plan from . import sf_production_demand_plan from . import sale_order +from . import stock_route +from . import stock_rule +from . import purchase_request +from . import purchase_order \ No newline at end of file diff --git a/sf_demand_plan/models/mrp_bom.py b/sf_demand_plan/models/mrp_bom.py new file mode 100644 index 00000000..8973cb87 --- /dev/null +++ b/sf_demand_plan/models/mrp_bom.py @@ -0,0 +1,20 @@ +from odoo import models, fields + + +class MrpBom(models.Model): + _inherit = 'mrp.bom' + + # 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom + def bom_create(self, product, bom_type, product_type, code=None): + bom_id = self.env['mrp.bom'].create({ + 'product_tmpl_id': product.product_tmpl_id.id, + 'type': bom_type, + # 'subcontractor_id': '' or subcontract.partner_id.id, + 'product_qty': 1, + 'product_uom_id': 1, + 'code': code + }) + if bom_type == 'subcontract' and product_type is not False: + subcontract = self.get_supplier(product.materials_type_id) + bom_id.subcontractor_id = subcontract.partner_id.id + return bom_id diff --git a/sf_demand_plan/models/mrp_production.py b/sf_demand_plan/models/mrp_production.py new file mode 100644 index 00000000..4a618cea --- /dev/null +++ b/sf_demand_plan/models/mrp_production.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models, api + + +class MrpProduction(models.Model): + _inherit = 'mrp.production' + + demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan", + string="需求计划明细", readonly=True) + + @api.depends('demand_plan_line_id') + def _compute_production_type(self): + for production in self: + if production.demand_plan_line_id.supply_method == 'automation': + production.production_type = '自动化产线加工' + elif production.demand_plan_line_id.supply_method == 'manual': + production.production_type = '人工线下加工' + else: + production.production_type = None + + def _get_purchase_request(self): + """获取跟制造订单相关的采购申请单""" + pr_ids = self.env['purchase.request'].sudo().search([('origin', '=', self.name)]) + return pr_ids diff --git a/sf_demand_plan/models/purchase_order.py b/sf_demand_plan/models/purchase_order.py new file mode 100644 index 00000000..9ee48bc0 --- /dev/null +++ b/sf_demand_plan/models/purchase_order.py @@ -0,0 +1,22 @@ +from odoo import api, fields, models, _ +from odoo.tools import float_compare + + +class PurchaseOrder(models.Model): + _inherit = 'purchase.order' + + demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan", + string="需求计划明细", readonly=True) + + @api.depends('origin', 'demand_plan_line_id') + def _compute_purchase_type(self): + for purchase in self: + if purchase.order_line[0].product_id.categ_id.name == '坯料': + if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协': + purchase.purchase_type = 'outsourcing' + else: + if purchase.demand_plan_line_id.supply_method == 'outsourcing': + purchase.purchase_type = 'outsourcing' + + elif purchase.demand_plan_line_id.supply_method == 'purchase': + purchase.purchase_type = 'outside' diff --git a/sf_demand_plan/models/purchase_request.py b/sf_demand_plan/models/purchase_request.py new file mode 100644 index 00000000..9cb15518 --- /dev/null +++ b/sf_demand_plan/models/purchase_request.py @@ -0,0 +1,110 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError, ValidationError + + +class PurchaseRequestLine(models.Model): + _inherit = 'purchase.request.line' + _description = '采购申请明细' + + supply_method = fields.Selection([ + ('automation', "自动化产线加工"), + ('manual', "人工线下加工"), + ('purchase', "外购"), + ('outsourcing', "委外加工"), + ], string='供货方式', readonly=True) + + demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan", + string="需求计划明细", readonly=True) + + @api.depends('demand_plan_line_id') + def _compute_supply_method(self): + for prl in self: + if prl.demand_plan_line_id: + prl.supply_method = prl.demand_plan_line_id.supply_method + else: + prl.supply_method = None + + +class PurchaseRequestLineMakePurchaseOrder(models.TransientModel): + _inherit = "purchase.request.line.make.purchase.order" + + def make_purchase_order(self): + res = [] + purchase_obj = self.env["purchase.order"] + po_line_obj = self.env["purchase.order.line"] + purchase = False + + if len(set([item_id.line_id.supply_method for item_id in self.item_ids])) > 1: + raise ValidationError('不同供货方式不可合并创建询价单!') + + for item in self.item_ids: + line = item.line_id + if item.product_qty <= 0.0: + raise UserError(_("Enter a positive quantity.")) + if self.purchase_order_id: + purchase = self.purchase_order_id + if not purchase: + po_data = self._prepare_purchase_order( + line.request_id.picking_type_id, + line.request_id.group_id, + line.company_id, + line.request_id.origin, + ) + po_data['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id + # po_data.update({'related_product':line.related_product.id}) + purchase = purchase_obj.create(po_data) + + # Look for any other PO line in the selected PO with same + # product and UoM to sum quantities instead of creating a new + # po line + domain = self._get_order_line_search_domain(purchase, item) + available_po_lines = po_line_obj.search(domain) + new_pr_line = True + # If Unit of Measure is not set, update from wizard. + if not line.product_uom_id: + line.product_uom_id = item.product_uom_id + # Allocation UoM has to be the same as PR line UoM + alloc_uom = line.product_uom_id + wizard_uom = item.product_uom_id + if available_po_lines and not item.keep_description: + new_pr_line = False + po_line = available_po_lines[0] + po_line.purchase_request_lines = [(4, line.id)] + po_line.move_dest_ids |= line.move_dest_ids + po_line_product_uom_qty = po_line.product_uom._compute_quantity( + po_line.product_uom_qty, alloc_uom + ) + wizard_product_uom_qty = wizard_uom._compute_quantity( + item.product_qty, alloc_uom + ) + all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty) + self.create_allocation(po_line, line, all_qty, alloc_uom) + else: + po_line_data = self._prepare_purchase_order_line(purchase, item) + if item.keep_description: + po_line_data["name"] = item.name + if line.related_product: + po_line_data.update({'related_product': line.related_product.id}) + po_line = po_line_obj.create(po_line_data) + po_line_product_uom_qty = po_line.product_uom._compute_quantity( + po_line.product_uom_qty, alloc_uom + ) + wizard_product_uom_qty = wizard_uom._compute_quantity( + item.product_qty, alloc_uom + ) + all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty) + self.create_allocation(po_line, line, all_qty, alloc_uom) + self._post_process_po_line(item, po_line, new_pr_line) + res.append(purchase.id) + + purchase_requests = self.item_ids.mapped("request_id") + purchase_requests.button_in_progress() + return { + "domain": [("id", "in", res)], + "name": _("RFQ"), + "view_mode": "tree,form", + "res_model": "purchase.order", + "view_id": False, + "context": False, + "type": "ir.actions.act_window", + } diff --git a/sf_demand_plan/models/sale_order.py b/sf_demand_plan/models/sale_order.py index 27e5c603..f4591cb2 100644 --- a/sf_demand_plan/models/sale_order.py +++ b/sf_demand_plan/models/sale_order.py @@ -9,14 +9,50 @@ class ReSaleOrder(models.Model): compute='_compute_mrp_production_ids', string='与此销售订单相关联的制造订单', groups='mrp.group_mrp_user', store=True) + demand_plan_count = fields.Integer( + "需求计划生成计数", + compute='_compute_demand_plan_count', + ) + #暂时不知道哪里用到了 + @api.depends('procurement_group_id') + def _compute_purchase_request_count(self): + for record in self: + pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', record.name)]) + if pr_ids: + record.purchase_request_purchase_order_count = len(pr_ids) + else: + record.purchase_request_purchase_order_count = 0 + #计算需求计划生成计数 + def _compute_demand_plan_count(self): + for line in self: + demand_plan = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', line.id)]) + line.demand_plan_count = len(demand_plan) def sale_order_create_line(self, product, item): ret = super(ReSaleOrder, self).sale_order_create_line(product, item) - vals = { - 'sale_order_id': ret.order_id.id, - 'sale_order_line_id': ret.id, - } - demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals) + # vals = { + # 'sale_order_id': ret.order_id.id, + # 'sale_order_line_id': ret.id, + # } + # demand_plan_info = self.env['sf.demand.main.plan'].sudo().create(vals) + # vals.update({'demand_plan_id': demand_plan_info.id, 'plan_uom_qty': ret.product_uom_qty}) + # demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals) + # demand_plan_info.write({'line_ids': demand_plan.ids}) + # 优化方案1:使用事务确保数据一致性 + with self.env.cr.savepoint(): + # 1. 先创建主计划 + demand_plan_info = self.env['sf.demand.main.plan'].sudo().create({ + 'sale_order_id': ret.order_id.id, + 'sale_order_line_id': ret.id, + }) + # 2. 创建明细计划时直接建立关联(利用One2many的inverse特性) + demand_plan = self.env['sf.production.demand.plan'].sudo().create({ + 'demand_plan_id': demand_plan_info.id, + 'plan_uom_qty': ret.product_uom_qty, + 'sale_order_id': ret.order_id.id, + 'sale_order_line_id': ret.id, + }) + # 3. 不需要手动更新line_ids,Odoo的ORM会自动处理One2many关系 if demand_plan.product_id.machining_drawings_name: filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] wizard_vals = { @@ -27,3 +63,21 @@ class ReSaleOrder(models.Model): } self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) return ret + #暂时不知道哪里用到 + def confirm_to_supply_method(self): + self.state = 'sale' + for line in self.order_line: + if line.product_id.auto_machining: + line.supply_method = 'automation' + #在销售订单打开需求计划列表 + def action_view_demand_plan(self): + self.ensure_one() + demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', self.id)]).ids + return { + 'res_model': 'sf.production.demand.plan', + 'type': 'ir.actions.act_window', + 'name': _("需求计划"), + 'domain': [('id', 'in', demand_plan_ids)], + 'view_mode': 'tree', + } + diff --git a/sf_demand_plan/models/sf_demand_main_plan.py b/sf_demand_plan/models/sf_demand_main_plan.py new file mode 100644 index 00000000..008ab8ac --- /dev/null +++ b/sf_demand_plan/models/sf_demand_main_plan.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api, _ +from odoo.tools import float_compare +from odoo.exceptions import ValidationError + + +class SfDemandMainPlan(models.Model): + _name = 'sf.demand.main.plan' + _description = 'sf_demand_main_plan' + + def _get_machining_precision(self): + machinings = self.env['sf.machining.accuracy'].sudo().search([]) + list = [(m.sync_id, m.name) for m in machinings] + return list + + state = fields.Selection([ + ('10', '需求确认'), + ('20', '待工艺设计'), + ('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') + 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(selection=_get_machining_precision, string='精度', + related='product_id.model_machining_precision') + + inventory_quantity_auto_apply = fields.Float( + string="成品库存", + compute='_compute_inventory_quantity_auto_apply', store=True + ) + + priority = fields.Selection([ + ('1', '紧急'), + ('2', '高'), + ('3', '中'), + ('4', '低'), + ], string='优先级', default='3') + + hide_button_release_plan = fields.Boolean( + string='显示下达计划按钮', + compute='_compute_hide_button_release_plan', + default=False + ) + + @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: + 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 + rounding = line.product_id.uom_id.rounding or 0.01#设置默认精度 + # 如果精度小于等于0,则设置为0.01 + if rounding<=0: + rounding = 0.01 + # 如果计划量小于等于0,则设置为0 + if float_compare(pending_qty, 0, precision_rounding=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 != '60')) + + def button_release_plan(self): + pass + + @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 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' + + def write(self, vals): + res = super(SfDemandMainPlan, self).write(vals) + line_ids = self.line_ids.filtered(lambda p: p.plan_uom_qty == 0 or p.plan_uom_qty < 0) + if line_ids: + raise ValidationError(f"计划量不能小于等于0") + 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.sale_order_id.name)) + return result diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index d07e4936..52ac539b 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -6,23 +6,29 @@ from odoo.exceptions import ValidationError from odoo.tools import float_compare from datetime import datetime, timedelta from odoo.exceptions import UserError +import re class SfProductionDemandPlan(models.Model): _name = 'sf.production.demand.plan' _description = 'sf_production_demand_plan' + def get_location_id(self): + stock_location = self.env['stock.location'].sudo().search([('name', '=', '客户')], limit=1) + return stock_location.id def _get_machining_precision(self): machinings = self.env['sf.machining.accuracy'].sudo().search([]) list = [(m.sync_id, m.name) for m in machinings] return list - + demand_plan_id = fields.Many2one(comodel_name="sf.demand.main.plan", + string="物料需求", readonly=True) priority = fields.Selection([ ('1', '紧急'), ('2', '高'), ('3', '中'), ('4', '低'), - ], string='优先级', default='3') + ], string='优先级', related='demand_plan_id.priority') + status = fields.Selection([ ('10', '草稿'), ('20', '待确认'), @@ -30,7 +36,21 @@ class SfProductionDemandPlan(models.Model): ('50', '待下达生产'), ('60', '已下达'), ('100', '取消'), - ], string='状态', compute='_compute_status', store=True) + ], string='状态', default='30', readonly=True) + + plan_uom_qty = fields.Float(string="计划量") + procurement_reason = fields.Selection([ + ('销售订单', "销售订单"), + ('需求预测', "需求预测"), + ('生产报废', "生产报废"), + ], string='补货原因', default='销售订单') + + blank_arrival_date = fields.Date('采购计划到货(坯料)') + finished_product_arrival_date = fields.Date('采购计划到货(成品)') + bom_id = fields.Many2one('mrp.bom', string="BOM", readonly=True) + location_id = fields.Many2one('stock.location', string='需求位置', default=get_location_id, readonly=True) + + sale_order_id = fields.Many2one(comodel_name="sale.order", string="销售订单", readonly=True) sale_order_line_id = fields.Many2one(comodel_name="sale.order.line", @@ -49,21 +69,23 @@ class SfProductionDemandPlan(models.Model): string='产品', store=True, index=True) model_id = fields.Char('模型ID', related='product_id.model_id') part_name = fields.Char('零件名称', related='product_id.part_name') - part_number = fields.Char('零件图号', compute='_compute_part_number', store=True) + part_number = fields.Char('零件图号', related='demand_plan_id.part_number') + #part_number = fields.Char('零件图号', compute='_compute_part_number', store=True) is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True) supply_method = fields.Selection([ ('automation', "自动化产线加工"), ('manual', "人工线下加工"), ('purchase', "外购"), ('outsourcing', "委外加工"), - ], string='供货方式', related='sale_order_line_id.supply_method', store=True) + ], string='供货方式', default='manual') 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) inventory_quantity_auto_apply = fields.Float( string="成品库存", - compute='_compute_inventory_quantity_auto_apply' + related='demand_plan_id.inventory_quantity_auto_apply' + #compute='_compute_inventory_quantity_auto_apply' ) qty_delivered = fields.Float( "交货数量", related='sale_order_line_id.qty_delivered') @@ -72,22 +94,31 @@ class SfProductionDemandPlan(models.Model): model_long = fields.Char('尺寸(mm)', compute='_compute_model_long') blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类', related='product_id.blank_type') - embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long') - materials_id = fields.Char('材料', compute='_compute_materials_id', store=True) + #embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long') + #materials_id = fields.Char('材料', compute='_compute_materials_id', store=True) + embryo_long = fields.Char('坯料尺寸(mm)', related='demand_plan_id.embryo_long') + materials_id = fields.Char('材料', related='demand_plan_id.materials_id') + model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度', related='product_id.model_machining_precision') - model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', - 'plan_process_parameter_rel', - string='表面工艺', - compute='_compute_model_process_parameters_ids' - , store=True - ) + #model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', + # 'plan_process_parameter_rel', + # string='表面工艺', + # compute='_compute_model_process_parameters_ids' + # , store=True + # ) + model_process_parameters_ids = fields.Many2many(related='demand_plan_id.model_process_parameters_ids', + string='表面工艺', ) + product_remark = fields.Char("产品备注", related='product_id.model_remark') order_code = fields.Char('E-SHOP订单号', related='sale_order_id.order_code') - order_state = fields.Selection( - string='订单状态', - related='sale_order_line_id.state') - route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True) + order_state = fields.Selection( string='订单状态',related='sale_order_line_id.state') + + #route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True) + route_ids = fields.Many2many('stock.route', 'stock_route_demand_plan', 'demand_plan_id', 'route_id', '库存路线', + domain=[('demand_plan_selectable', '=', True)], compute='_compute_route_ids', + store=True) + contract_date = fields.Date('合同日期', related='sale_order_id.contract_date') date_order = fields.Datetime('下单日期', related='sale_order_id.date_order') contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True) @@ -103,70 +134,60 @@ class SfProductionDemandPlan(models.Model): print_count = fields.Char('打印次数', default='T0C0', readonly=True) sequence = fields.Integer('序号') - hide_action_open_mrp_production = fields.Boolean( - string='显示待工艺确认按钮', - compute='_compute_hid_button', + mrp_production_ids = fields.Many2many( + 'mrp.production', + compute='_compute_mrp_production_ids', + string='与此相需求计划关联的制造订单', + store=True) + hide_release_production_order = fields.Boolean( + string='显示下发生产按钮', + compute='_compute_hide_release_production_order', default=False ) - hide_action_purchase_orders = fields.Boolean( - string='显示采购按钮', - compute='_compute_hide_action_purchase_orders', - default=False - ) + # hide_action_open_mrp_production = fields.Boolean( + # string='显示待工艺确认按钮', + # compute='_compute_hid_button', + # default=False + # ) + # + # hide_action_purchase_orders = fields.Boolean( + # string='显示采购按钮', + # compute='_compute_hide_action_purchase_orders', + # default=False + # ) + # + # hide_action_stock_picking = fields.Boolean( + # string='显示调拨单按钮', + # compute='_compute_hide_action_stock_picking', + # default=False + # ) + # + # hide_action_outsourcing_stock_picking = fields.Boolean( + # string='委外显示调拨单按钮', + # compute='_compute_hide_action_stock_picking', + # default=False + # ) + # + # hide_action_view_programming = fields.Boolean( + # string='显示编程单按钮', + # compute='_compute_hid_button', + # default=False + # ) + # + # outsourcing_purchase_request = fields.Char('委外采购申请单') + @api.depends('supply_method') + def _compute_route_ids(self): + for pdp in self: + if pdp.supply_method: + group_id = self.env['stock.route.group'].sudo().search([('code', '=', pdp.supply_method)]) + route_ids = self.env['stock.route'].sudo().search( + [('demand_plan_selectable', '=', True), ('stock_route_group_ids', '=', group_id.id)]) + if route_ids: + pdp.route_ids = route_ids.ids + continue + pdp.route_ids = None - hide_action_stock_picking = fields.Boolean( - string='显示调拨单按钮', - compute='_compute_hide_action_stock_picking', - default=False - ) - - hide_action_outsourcing_stock_picking = fields.Boolean( - string='委外显示调拨单按钮', - compute='_compute_hide_action_stock_picking', - default=False - ) - - hide_action_view_programming = fields.Boolean( - string='显示编程单按钮', - compute='_compute_hid_button', - default=False - ) - - outsourcing_purchase_request = fields.Char('委外采购申请单') - - @api.depends('sale_order_id.state', 'sale_order_id.mrp_production_ids.schedule_state', 'sale_order_id.order_line', - 'sale_order_id.mrp_production_ids.state') - def _compute_status(self): - for record in self: - if record.sale_order_id: - sale_order_state = record.sale_order_id.state - if sale_order_state in ('draft', 'sent', 'supply method'): - record.status = '20' # 待确认 - if record.supply_method in ('purchase', 'outsourcing') and sale_order_state in ( - 'sale', 'processing', 'physical_distribution', 'delivered', - 'done') and sale_order_state != 'cancel': - record.status = '60' # 已下达 - if record.supply_method in ('automation', 'manual'): - if sale_order_state in ( - 'sale', 'processing', 'physical_distribution', 'delivered', - 'done') and sale_order_state != 'cancel': - record.status = '30' # 需求确认 - # 检查所有制造订单的排程单状态,有一个为待排程状态,就为待下达生产 - pending_productions = record.sale_order_id.mrp_production_ids.filtered( - lambda p: p.state == 'confirmed' and p.product_id.id == record.product_id.id - ) - if pending_productions: - record.status = '50' # 待下达生产 - # 检查所有制造订单的排程单状态 - if record.sale_order_id.mrp_production_ids: - product_productions = record.sale_order_id.mrp_production_ids.filtered( - lambda p: p.product_id.id == record.product_id.id - ) - if product_productions and all(order.schedule_state != '未排' for order in product_productions): - record.status = '60' # 已下达 - if sale_order_state == 'cancel' or not record.sale_order_line_id: - record.status = '100' # 取消 @api.depends('sale_order_line_id.product_id.name') def _compute_sale_order_line_number(self): @@ -176,18 +197,6 @@ class SfProductionDemandPlan(models.Model): else: line.sale_order_line_number = None - @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.length', 'product_id.width', 'product_id.height') def _compute_model_long(self): for line in self: @@ -196,51 +205,6 @@ class SfProductionDemandPlan(models.Model): else: line.model_long = 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: - 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.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_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('sale_order_id.mrp_production_ids.workorder_ids.date_start') def _compute_actual_start_date(self): for record in self: @@ -303,17 +267,34 @@ class SfProductionDemandPlan(models.Model): else: record.material_check = None + @api.depends('status') + def _compute_mrp_production_ids(self): + for record in self: + if record.status in ('50', '60'): + record.mrp_production_ids = self.env['mrp.production'].sudo().search( + [('demand_plan_line_id', '=', record.id)]).ids + else: + record.mrp_production_ids = None + + @api.depends('mrp_production_ids.state') + def _compute_hide_release_production_order(self): + for record in self: + # 检查这条需求计划所有制造订单的排程单状态,有一个为待排程状态,就显示该按钮 + record.hide_release_production_order = bool(record.mrp_production_ids.filtered( + lambda p: p.state == 'confirmed' + )) @api.constrains('planned_start_date') def _check_planned_start_date(self): for record in self: if record.planned_start_date and record.planned_start_date < fields.Date.today(): raise ValidationError("计划开工日期必须大于或等于今天。") - def release_production_order(self): + def button_release_production(self): + self.ensure_one() if not self.planned_start_date: raise ValidationError("请先填写计划开工日期") - pro_plan_list = self.env['sf.production.plan'].search( - [('product_id', '=', self.product_id.id), ('state', '=', 'draft')]) + pro_plan_list = self.env['sf.production.plan'].sudo().search( + [('production_id', 'in', self.mrp_production_ids.ids), ('state', '=', 'draft')]) sf_production_line = self.env['sf.production.line'].sudo().search( [('name', '=', '1#CNC自动生产线')], limit=1) if sf_production_line: @@ -324,11 +305,19 @@ class SfProductionDemandPlan(models.Model): pro_plan_list.production_line_id = sf_production_line.id pro_plan_list.date_planned_start = date_planned_start self._do_production_schedule(pro_plan_list) + self.status = '60' + self.update_sale_order_state() def _do_production_schedule(self, pro_plan_list): for pro_plan in pro_plan_list: pro_plan.do_production_schedule() + def update_sale_order_state(self): + demand_plan = self.env['sf.demand.main.plan'].sudo().search([('sale_order_id', '=', self.sale_order_id.id)]) + demand_plan_state = demand_plan.filtered(lambda line: line.state != '40') + if not demand_plan_state: + # 修改销售订单为加工中 + self.sale_order_id.state = 'processing' def button_action_print(self): return { @@ -340,211 +329,481 @@ class SfProductionDemandPlan(models.Model): 'target': 'new', } - @api.depends('sale_order_id.mrp_production_ids.state', 'sale_order_id.mrp_production_ids.programming_state') - def _compute_hid_button(self): - for record in self: - mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered( - lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == record.product_id.id - ) - record.hide_action_open_mrp_production = bool(mrp_production_ids) and record.supply_method in ( - 'automation', 'manual') - programming_mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered( - lambda p: p.programming_state == '编程中' and p.product_id.id == record.product_id.id - ) - record.hide_action_view_programming = bool(programming_mrp_production_ids) + # @api.depends('sale_order_id.mrp_production_ids.state', 'sale_order_id.mrp_production_ids.programming_state') + # def _compute_hid_button(self): + # for record in self: + # mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered( + # lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == record.product_id.id + # ) + # record.hide_action_open_mrp_production = bool(mrp_production_ids) and record.supply_method in ( + # 'automation', 'manual') + # programming_mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered( + # lambda p: p.programming_state == '编程中' and p.product_id.id == record.product_id.id + # ) + # record.hide_action_view_programming = bool(programming_mrp_production_ids) + # + # def _compute_hide_action_purchase_orders(self): + # for record in self: + # record.hide_action_purchase_orders = False + # outsourcing_purchase_request = [] + # if record.supply_method in ('automation', + # 'manual') and record.material_check == '0' and not record.sale_order_line_id.is_incoming_material: + # mrp_production = record.sale_order_id.mrp_production_ids.filtered( + # lambda p: p.product_id.id == record.product_id.id + # ).sorted(key=lambda p: p.id) + # if mrp_production: + # raw_materials = mrp_production.mapped('move_raw_ids.product_id') + # if raw_materials: + # purchase_orders = self.env['purchase.order'].sudo().search([ + # ('state', '=', 'purchase'), + # ('order_line.product_id', 'in', raw_materials.ids) + # ]) + # total_purchase_quantity = sum( + # sum( + # order.order_line.filtered( + # lambda line: line.product_id in raw_materials + # ).mapped('product_qty') + # ) + # for order in purchase_orders + # ) + # if float_compare(total_purchase_quantity, record.product_uom_qty, + # precision_rounding=record.product_id.uom_id.rounding) == -1: + # pr_ids = self.env['purchase.request'].sudo().search( + # [('line_ids.product_id', 'in', raw_materials.ids), ('state', '!=', 'done')]) + # outsourcing_purchase_request.extend(pr_ids.ids) + # elif record.supply_method in ('purchase', 'outsourcing'): + # purchase_orders = self.env['purchase.order'].sudo().search([ + # ('state', 'in', ('purchase', 'done')), + # ('order_line.product_id', '=', record.product_id.id) + # ]) + # total_purchase_quantity = sum( + # sum( + # order.order_line.filtered( + # lambda line: line.product_id in record.product_id + # ).mapped('product_qty') + # ) + # for order in purchase_orders + # ) + # + # if float_compare(total_purchase_quantity, record.product_uom_qty, + # precision_rounding=record.product_id.uom_id.rounding) == -1: + # pr_ids = self.env['purchase.request'].sudo().search( + # [('origin', 'like', record.sale_order_id.name), ('state', '!=', 'done')]) + # outsourcing_purchase_request.extend(pr_ids.ids) + # if record.supply_method == 'outsourcing' and not record.sale_order_line_id.is_incoming_material: + # bom_line_ids = record.product_id.bom_ids.bom_line_ids + # if bom_line_ids: + # # BOM_数量 + # total_product_qty = sum(line.product_qty for line in bom_line_ids) + # bom_product_ids = bom_line_ids.mapped('product_id') + # product_purchase_orders = self.env['purchase.order'].sudo().search([ + # ('state', 'in', ('purchase', 'done')), + # ('order_line.product_id', 'in', bom_product_ids.ids) + # ]) + # # 购订单_数量 + # total_outsourcing_purchase_quantity = sum( + # sum( + # order.order_line.filtered( + # lambda line: line.product_id in bom_product_ids + # ).mapped('product_qty') + # ) + # for order in product_purchase_orders + # ) + # quantity = total_outsourcing_purchase_quantity / total_product_qty + # if float_compare(quantity, record.product_uom_qty, + # precision_rounding=record.product_id.uom_id.rounding) == -1: + # purchase_request = self.env['purchase.request'].sudo().search( + # [('line_ids.product_id', 'in', bom_product_ids.ids), + # ('line_ids.purchase_state', 'not in', ('purchase', 'done')), ('state', '!=', 'done')]) + # outsourcing_purchase_request.extend(purchase_request.ids) + # record.outsourcing_purchase_request = json.dumps(outsourcing_purchase_request) + # if outsourcing_purchase_request: + # record.hide_action_purchase_orders = True + # + # @api.depends('sale_order_id.mrp_production_ids.picking_ids.state', 'sale_order_id.picking_ids.state') + # def _compute_hide_action_stock_picking(self): + # for record in self: + # record.hide_action_stock_picking = False + # record.hide_action_outsourcing_stock_picking = False + # if record.supply_method in ('automation', 'manual'): + # manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered( + # lambda p: p.product_id.id == record.product_id.id + # ) + # record.hide_action_stock_picking = bool(manufacturing_orders.mapped('picking_ids').filtered( + # lambda p: p.state == 'assigned')) + # elif record.supply_method in ('purchase', 'outsourcing'): + # assigned_picking_ids = record.sale_order_id.picking_ids.filtered( + # lambda + # p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in record.product_id) + # if record.supply_method == 'outsourcing': + # outsourcing_assigned_picking_ids = record.get_outsourcing_picking_ids() + # record.hide_action_outsourcing_stock_picking = outsourcing_assigned_picking_ids + # record.hide_action_stock_picking = assigned_picking_ids or outsourcing_assigned_picking_ids + # else: + # record.hide_action_stock_picking = assigned_picking_ids + # + # def get_outsourcing_picking_ids(self): + # order_ids = self.env['purchase.order'].sudo().search( + # [('order_line.product_id', 'in', self.product_id.ids), + # ('purchase_type', '=', 'outsourcing')]) + # outsourcing_picking_ids = order_ids._get_subcontracting_resupplies() + # outsourcing_assigned_picking_ids = outsourcing_picking_ids.filtered(lambda p: p.state == 'assigned') + # return outsourcing_assigned_picking_ids - def _compute_hide_action_purchase_orders(self): - for record in self: - record.hide_action_purchase_orders = False - outsourcing_purchase_request = [] - if record.supply_method in ('automation', - 'manual') and record.material_check == '0' and not record.sale_order_line_id.is_incoming_material: - mrp_production = record.sale_order_id.mrp_production_ids.filtered( - lambda p: p.product_id.id == record.product_id.id - ).sorted(key=lambda p: p.id) - if mrp_production: - raw_materials = mrp_production.mapped('move_raw_ids.product_id') - if raw_materials: - purchase_orders = self.env['purchase.order'].sudo().search([ - ('state', '=', 'purchase'), - ('order_line.product_id', 'in', raw_materials.ids) - ]) - total_purchase_quantity = sum( - sum( - order.order_line.filtered( - lambda line: line.product_id in raw_materials - ).mapped('product_qty') - ) - for order in purchase_orders - ) - if float_compare(total_purchase_quantity, record.product_uom_qty, - precision_rounding=record.product_id.uom_id.rounding) == -1: - pr_ids = self.env['purchase.request'].sudo().search( - [('line_ids.product_id', 'in', raw_materials.ids), ('state', '!=', 'done')]) - outsourcing_purchase_request.extend(pr_ids.ids) - elif record.supply_method in ('purchase', 'outsourcing'): - purchase_orders = self.env['purchase.order'].sudo().search([ - ('state', 'in', ('purchase', 'done')), - ('order_line.product_id', '=', record.product_id.id) - ]) - total_purchase_quantity = sum( - sum( - order.order_line.filtered( - lambda line: line.product_id in record.product_id - ).mapped('product_qty') - ) - for order in purchase_orders - ) + # def action_open_sale_order(self): + # self.ensure_one() + # return { + # 'type': 'ir.actions.act_window', + # 'res_model': 'sale.order', + # 'res_id': self.sale_order_id.id, + # 'view_mode': 'form', + # } - if float_compare(total_purchase_quantity, record.product_uom_qty, - precision_rounding=record.product_id.uom_id.rounding) == -1: - pr_ids = self.env['purchase.request'].sudo().search( - [('origin', 'like', record.sale_order_id.name), ('state', '!=', 'done')]) - outsourcing_purchase_request.extend(pr_ids.ids) - if record.supply_method == 'outsourcing' and not record.sale_order_line_id.is_incoming_material: - bom_line_ids = record.product_id.bom_ids.bom_line_ids - if bom_line_ids: - # BOM_数量 - total_product_qty = sum(line.product_qty for line in bom_line_ids) - bom_product_ids = bom_line_ids.mapped('product_id') - product_purchase_orders = self.env['purchase.order'].sudo().search([ - ('state', 'in', ('purchase', 'done')), - ('order_line.product_id', 'in', bom_product_ids.ids) - ]) - # 购订单_数量 - total_outsourcing_purchase_quantity = sum( - sum( - order.order_line.filtered( - lambda line: line.product_id in bom_product_ids - ).mapped('product_qty') - ) - for order in product_purchase_orders - ) - quantity = total_outsourcing_purchase_quantity / total_product_qty - if float_compare(quantity, record.product_uom_qty, - precision_rounding=record.product_id.uom_id.rounding) == -1: - purchase_request = self.env['purchase.request'].sudo().search( - [('line_ids.product_id', 'in', bom_product_ids.ids), - ('line_ids.purchase_state', 'not in', ('purchase', 'done')), ('state', '!=', 'done')]) - outsourcing_purchase_request.extend(purchase_request.ids) - record.outsourcing_purchase_request = json.dumps(outsourcing_purchase_request) - if outsourcing_purchase_request: - record.hide_action_purchase_orders = True + # def action_open_mrp_production(self): + # self.ensure_one() + # mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered( + # lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == self.product_id.id + # ) + # action = { + # 'res_model': 'mrp.production', + # 'type': 'ir.actions.act_window', + # } + # if len(mrp_production_ids) == 1: + # action.update({ + # 'view_mode': 'form', + # 'res_id': mrp_production_ids.id, + # }) + # else: + # action.update({ + # 'name': _("制造订单列表"), + # 'domain': [('id', 'in', mrp_production_ids.ids)], + # 'view_mode': 'tree,form', + # }) + # return action - @api.depends('sale_order_id.mrp_production_ids.picking_ids.state', 'sale_order_id.picking_ids.state') - def _compute_hide_action_stock_picking(self): - for record in self: - record.hide_action_stock_picking = False - record.hide_action_outsourcing_stock_picking = False - if record.supply_method in ('automation', 'manual'): - manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered( - lambda p: p.product_id.id == record.product_id.id - ) - record.hide_action_stock_picking = bool(manufacturing_orders.mapped('picking_ids').filtered( - lambda p: p.state == 'assigned')) - elif record.supply_method in ('purchase', 'outsourcing'): - assigned_picking_ids = record.sale_order_id.picking_ids.filtered( - lambda - p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in record.product_id) - if record.supply_method == 'outsourcing': - outsourcing_assigned_picking_ids = record.get_outsourcing_picking_ids() - record.hide_action_outsourcing_stock_picking = outsourcing_assigned_picking_ids - record.hide_action_stock_picking = assigned_picking_ids or outsourcing_assigned_picking_ids - else: - record.hide_action_stock_picking = assigned_picking_ids + # def action_view_purchase_request(self): + # self.ensure_one() + # pr_ids = self.env['purchase.request'].sudo().search( + # [('id', 'in', ast.literal_eval(self.outsourcing_purchase_request))]) + # action = { + # 'res_model': 'purchase.request', + # 'type': 'ir.actions.act_window', + # } + # if len(pr_ids) == 1: + # action.update({ + # 'view_mode': 'form', + # 'res_id': pr_ids[0].id, + # }) + # else: + # action.update({ + # 'name': _("采购申请"), + # 'domain': [('id', 'in', pr_ids.ids)], + # 'view_mode': 'tree,form', + # }) + # return action - def get_outsourcing_picking_ids(self): - order_ids = self.env['purchase.order'].sudo().search( - [('order_line.product_id', 'in', self.product_id.ids), - ('purchase_type', '=', 'outsourcing')]) - outsourcing_picking_ids = order_ids._get_subcontracting_resupplies() - outsourcing_assigned_picking_ids = outsourcing_picking_ids.filtered(lambda p: p.state == 'assigned') - return outsourcing_assigned_picking_ids + # def action_view_stock_picking(self): + # self.ensure_one() + # action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_all") + # picking_ids = None + # if self.supply_method in ('automation', 'manual'): + # mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered( + # lambda p: p.product_id.id == self.product_id.id + # ) + # picking_ids = mrp_production_ids.mapped('picking_ids').filtered( + # lambda p: p.state == 'assigned') + # elif self.supply_method in ('purchase', 'outsourcing'): + # picking_ids = self.sale_order_id.picking_ids.filtered( + # lambda + # p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in self.product_id) + # if self.supply_method == 'outsourcing' and self.hide_action_outsourcing_stock_picking: + # picking_ids = picking_ids.union(self.get_outsourcing_picking_ids()) + # if picking_ids: + # if len(picking_ids) > 1: + # action['domain'] = [('id', 'in', picking_ids.ids)] + # elif picking_ids: + # action['res_id'] = picking_ids.id + # action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] + # if 'views' in action: + # action['views'] += [(state, view) for state, view in action['views'] if view != 'form'] + # return action - def action_open_sale_order(self): + # def action_view_programming(self): + # self.ensure_one() + # programming_mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered( + # lambda p: p.programming_state == '编程中' and p.product_id.id == self.product_id.id + # ).mapped('programming_no') + # if programming_mrp_production_ids: + # programming_no = list(set(programming_mrp_production_ids)) + # numbers_str = "、".join(programming_no) + # raise ValidationError(f"编程单号:{numbers_str},请去云平台处理") + + def action_edit_demand_plan_form(self): + """编辑需求计划详细信息""" self.ensure_one() - return { + action = { + 'name': _("需求计划"), 'type': 'ir.actions.act_window', - 'res_model': 'sale.order', - 'res_id': self.sale_order_id.id, 'view_mode': 'form', + 'views': [[self.env.ref('sf_demand_plan.view_sf_demand_main_plan_form').id, 'form']], + 'res_model': 'sf.demand.main.plan', + 'res_id': self.demand_plan_id.id, } - - def action_open_mrp_production(self): + #打开需求计划视图时,如果状态时已下达,则只读,不能添加明细了 + if self.demand_plan_id.state == '40': + action.update({'flags': {'mode': 'readonly'}}) + return action + #删除需求计划 + def button_delete(self): self.ensure_one() - mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered( - lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == self.product_id.id + if len(self.demand_plan_id.line_ids) == 1: + raise ValidationError(f"最后一条计划,不能删除!") + self.unlink() + #需求明细行里下达需求计划 + def button_release_plan(self): + self.ensure_one() + if not self.supply_method: + raise ValidationError(f"供货方式不能为空!") + #点击时校验,累计计划量>需求数量,则提示,新增了个提示确认视图 + if self.plan_uom_qty > self.product_uom_qty: + 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': self.id, + 'default_release_message': f"您正在下达计划量 {self.plan_uom_qty},累计计划量为 {self.demand_plan_id.planned_qty},需求数量为 {self.product_uom_qty},已超过需求数量,是否继续?", + }} + self.action_confirm() + + def action_confirm(self): + self = self.with_context( + demand_plan_line_id=self.id ) - action = { - 'res_model': 'mrp.production', - 'type': 'ir.actions.act_window', - } - if len(mrp_production_ids) == 1: - action.update({ - 'view_mode': 'form', - 'res_id': mrp_production_ids.id, - }) - else: - action.update({ - 'name': _("制造订单列表"), - 'domain': [('id', 'in', mrp_production_ids.ids)], - 'view_mode': 'tree,form', - }) - return action - - def action_view_purchase_request(self): - self.ensure_one() - pr_ids = self.env['purchase.request'].sudo().search( - [('id', 'in', ast.literal_eval(self.outsourcing_purchase_request))]) - action = { - 'res_model': 'purchase.request', - 'type': 'ir.actions.act_window', - } - if len(pr_ids) == 1: - action.update({ - 'view_mode': 'form', - 'res_id': pr_ids[0].id, - }) - else: - action.update({ - 'name': _("采购申请"), - 'domain': [('id', 'in', pr_ids.ids)], - 'view_mode': 'tree,form', - }) - return action - - def action_view_stock_picking(self): - self.ensure_one() - action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_all") - picking_ids = None + self.mrp_bom_create() + self._action_launch_stock_rule() + #自动化产线,人工产线,待下达生产 if self.supply_method in ('automation', 'manual'): - mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered( - lambda p: p.product_id.id == self.product_id.id - ) - picking_ids = mrp_production_ids.mapped('picking_ids').filtered( - lambda p: p.state == 'assigned') - elif self.supply_method in ('purchase', 'outsourcing'): - picking_ids = self.sale_order_id.picking_ids.filtered( - lambda - p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in self.product_id) - if self.supply_method == 'outsourcing' and self.hide_action_outsourcing_stock_picking: - picking_ids = picking_ids.union(self.get_outsourcing_picking_ids()) - if picking_ids: - if len(picking_ids) > 1: - action['domain'] = [('id', 'in', picking_ids.ids)] - elif picking_ids: - action['res_id'] = picking_ids.id - action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] - if 'views' in action: - action['views'] += [(state, view) for state, view in action['views'] if view != 'form'] - return action + self.write({'status': '50'}) + else: + #外购,委外,已下达 + self.write({'status': '60'}) + self.update_sale_order_state() - def action_view_programming(self): + def mrp_bom_create(self): + # 检查是否已有相同供货方式的BOM + if self.supply_method in ('automation', 'manual'): + #同一需求计划下,相同供货方式的产品可以共享BOM + 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 # 复用已有BOM + 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_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单据编码 + code = f"{self.product_id.default_code}-{bom_type}-{datetime.now().strftime('%Y%m%d%H%M%S')}" + + order_id = self.sale_order_id + product = self.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, + '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) + # 获取成品名结尾-n的n + product_seria = int(product_name.split('-')[-1]) + + # 成品供货方式为采购则不生成bom + if self.supply_method != 'purchase': + # 当成品上带有客供料选项时,生成坯料时选择“客供料”路线 + if self.sale_order_line_id.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', code) + product_bom_material_customer_provided.with_user( + self.env.ref("base.user_admin")).bom_create_line_has( + material_customer_provided_embryo) + self.bom_id = product_bom_material_customer_provided.id + elif self.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', code) + product_bom_self_machining.with_user(self.env.ref("base.user_admin")).bom_create_line_has( + self_machining_embryo) + self.bom_id = product_bom_self_machining.id + elif self.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', code) + product_bom_outsource.with_user(self.env.ref("base.user_admin")).bom_create_line_has( + outsource_embryo) + self.bom_id = product_bom_outsource.id + elif self.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 and 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', code) + product_bom_purchase.with_user(self.env.ref("base.user_admin")).bom_create_line_has( + purchase_embryo) + self.bom_id = product_bom_purchase.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: + 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 + + 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, + } + + def _prepare_procurement_values(self, group_id=False): self.ensure_one() - programming_mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered( - lambda p: p.programming_state == '编程中' and p.product_id.id == self.product_id.id - ).mapped('programming_no') - if programming_mrp_production_ids: - programming_no = list(set(programming_mrp_production_ids)) - numbers_str = "、".join(programming_no) - raise ValidationError(f"编程单号:{numbers_str},请去云平台处理") + 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 + + + diff --git a/sf_demand_plan/models/stock_route.py b/sf_demand_plan/models/stock_route.py new file mode 100644 index 00000000..7719d7b1 --- /dev/null +++ b/sf_demand_plan/models/stock_route.py @@ -0,0 +1,49 @@ +from odoo import models, fields, api, _ + + +class SfStockRoute(models.Model): + _inherit = 'stock.route' + + demand_plan_selectable = fields.Boolean("需求计划行") + stock_route_group_ids = fields.Many2many('stock.route.group', 'route_to_group', string='路线组') + demand_plan_ids = fields.Many2many('sf.production.demand.plan', 'stock_route_demand_plan', 'route_id', + 'demand_plan_id', '需求计划', copy=False, compute='_compute_demand_plan_ids', + store=True) + + @api.depends('demand_plan_selectable', 'stock_route_group_ids') + def _compute_demand_plan_ids(self): + for sr in self: + if sr.demand_plan_selectable: + stock_route_group = [srg.code for srg in sr.stock_route_group_ids] + demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search( + [('supply_method', 'in', stock_route_group)]) + if demand_plan_ids: + sr.demand_plan_ids = demand_plan_ids.ids + continue + sr.demand_plan_ids = None + + # def name_get(self): + # res = super().name_get() + # if self.env.context.get('demand_plan_search_stock_route_id'): + # demand_plan_id = self.env['sf.production.demand.plan'].sudo().browse( + # int(self.env.context.get('demand_plan_search_stock_route_id'))) + # if demand_plan_id and demand_plan_id.supply_method: + # supply_method = self._set_supply_method(demand_plan_id.supply_method) + # res = [(item[0], f'{item[1]}-{supply_method}') for item in res if len(item) == 2] + # return res + # + # def _set_supply_method(self, supply_method): + # return { + # 'automation': "自动化产线加工", + # 'manual': "人工线下加工", + # 'purchase': "外购", + # 'outsourcing': "委外加工" + # }.get(supply_method) + + +class SfStockRouteGroup(models.Model): + _name = 'stock.route.group' + _description = '路线组' + + name = fields.Char('名称') + code = fields.Char('编码') diff --git a/sf_demand_plan/models/stock_rule.py b/sf_demand_plan/models/stock_rule.py new file mode 100644 index 00000000..fe9c07eb --- /dev/null +++ b/sf_demand_plan/models/stock_rule.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import api, fields, models + + +class StockRule(models.Model): + _inherit = 'stock.rule' + + def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values, + bom): + res = super()._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, company_id, + values, bom) + if self.env.context.get('demand_plan_line_id'): + res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id') + return res + + @api.model + def _prepare_purchase_request_line(self, request_id, procurement): + res = super()._prepare_purchase_request_line(request_id, procurement) + if self.env.context.get('demand_plan_line_id'): + res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id') + return res diff --git a/sf_demand_plan/security/ir.model.access.csv b/sf_demand_plan/security/ir.model.access.csv index 56e8e247..5bb41d0d 100644 --- a/sf_demand_plan/security/ir.model.access.csv +++ b/sf_demand_plan/security/ir.model.access.csv @@ -1,6 +1,15 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0 -access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,0,0 +access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,1,1 access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0 access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0 + +access_sf_demand_main_plan,sf.demand.main.plan,model_sf_demand_main_plan,base.group_user,1,0,0,0 +access_sf_demand_main_plan_for_dispatch,sf.demand.main.plan for dispatch,model_sf_demand_main_plan,sf_base.group_plan_dispatch,1,1,0,0 + +access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0 +access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0 + +access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0 +access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1 \ No newline at end of file diff --git a/sf_demand_plan/views/demand_main_plan.xml b/sf_demand_plan/views/demand_main_plan.xml new file mode 100644 index 00000000..298d1f97 --- /dev/null +++ b/sf_demand_plan/views/demand_main_plan.xml @@ -0,0 +1,70 @@ + + + sf.demand.main.plan.form + sf.demand.main.plan + +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/sf_demand_plan/wizard/__init__.py b/sf_demand_plan/wizard/__init__.py index 170f9e85..2efdcf3e 100644 --- a/sf_demand_plan/wizard/__init__.py +++ b/sf_demand_plan/wizard/__init__.py @@ -1 +1,2 @@ from . import sf_demand_plan_print_wizard +from . import sf_release_plan_wizard \ No newline at end of file diff --git a/sf_demand_plan/wizard/sf_release_plan_wizard.py b/sf_demand_plan/wizard/sf_release_plan_wizard.py new file mode 100644 index 00000000..bd2e00ac --- /dev/null +++ b/sf_demand_plan/wizard/sf_release_plan_wizard.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +import logging +from odoo import models, fields, api, _ + +_logger = logging.getLogger(__name__) + + +class SfReleasePlanWizard(models.TransientModel): + _name = 'sf.release.plan.wizard' + _description = u'下达计划向导' + + demand_plan_id = fields.Many2one(comodel_name="sf.demand.main.plan", + string="需求计划", readonly=True) + + demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan", + string="需求计划明细", readonly=True) + + release_message = fields.Char(string='提示', readonly=True) + + def confirm(self): + if self.demand_plan_line_id: + self.demand_plan_line_id.action_confirm() diff --git a/sf_demand_plan/wizard/sf_release_plan_wizard_views.xml b/sf_demand_plan/wizard/sf_release_plan_wizard_views.xml new file mode 100644 index 00000000..716093b6 --- /dev/null +++ b/sf_demand_plan/wizard/sf_release_plan_wizard_views.xml @@ -0,0 +1,22 @@ + + + + sf.release.plan.wizard.form + sf.release.plan.wizard + +
+ +
+
+ +
+
+ +
+
+
+
+
\ No newline at end of file