From f41d3558d28eb53074b006a0f3f9d85614bb245b Mon Sep 17 00:00:00 2001 From: guanhuan Date: Tue, 24 Jun 2025 17:46:55 +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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/__manifest__.py | 4 +- sf_demand_plan/migrations/1.1/post-migrate.py | 25 ++++++++ sf_demand_plan/models/sale_order.py | 4 +- sf_demand_plan/models/sf_demand_plan.py | 62 ++++++++++++++++--- .../models/sf_production_demand_plan.py | 47 +++++++++----- sf_demand_plan/security/ir.model.access.csv | 2 +- sf_demand_plan/views/demand_plan.xml | 7 ++- sf_demand_plan/views/demand_plan_info.xml | 36 ++++++++++- 8 files changed, 151 insertions(+), 36 deletions(-) create mode 100644 sf_demand_plan/migrations/1.1/post-migrate.py diff --git a/sf_demand_plan/__manifest__.py b/sf_demand_plan/__manifest__.py index dc772ab2..c7df6fab 100644 --- a/sf_demand_plan/__manifest__.py +++ b/sf_demand_plan/__manifest__.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. { 'name': '机企猫智能工厂 需求计划', - 'version': '1.0', + 'version': '1.1', 'summary': '智能工厂计划管理', 'sequence': 1, 'description': """ @@ -13,7 +13,7 @@ 'depends': ['sf_plan','jikimo_printing'], 'data': [ 'security/ir.model.access.csv', - 'views/demand_plan_info.xml.xml', + 'views/demand_plan_info.xml', 'views/demand_plan.xml', 'wizard/sf_demand_plan_print_wizard_view.xml', ], 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..5f2a0286 --- /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.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/sale_order.py b/sf_demand_plan/models/sale_order.py index 620bc3b3..c7ece36d 100644 --- a/sf_demand_plan/models/sale_order.py +++ b/sf_demand_plan/models/sale_order.py @@ -17,9 +17,9 @@ class ReSaleOrder(models.Model): 'sale_order_line_id': ret.id, } demand_plan_info = self.env['sf.demand.plan'].sudo().create(vals) - vals.update({'demand_plan_id': demand_plan_info.id}) + 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.id}) + demand_plan_info.write({'line_ids': demand_plan.ids}) if demand_plan.product_id.machining_drawings_name: filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] wizard_vals = { diff --git a/sf_demand_plan/models/sf_demand_plan.py b/sf_demand_plan/models/sf_demand_plan.py index 14c6a9de..b57ed713 100644 --- a/sf_demand_plan/models/sf_demand_plan.py +++ b/sf_demand_plan/models/sf_demand_plan.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo import models, fields, api, _ +from odoo.tools import float_compare class SfDemandPlan(models.Model): @@ -17,7 +18,8 @@ class SfDemandPlan(models.Model): ('20', '待工艺设计'), ('30', '部分下达'), ('40', '已下达'), - ], string='状态') + ('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) @@ -40,12 +42,12 @@ class SfDemandPlan(models.Model): 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) - # product_uom_qty = fields.Float( - # string="待计划", - # related='sale_order_line_id.product_uom_qty', store=True) - # product_uom_qty = fields.Float( - # string="已计划", - # related='sale_order_line_id.product_uom_qty', 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( @@ -56,7 +58,7 @@ class SfDemandPlan(models.Model): contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True) model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', - 'plan_process_parameter_rel', + 'demand_plan_process_parameter_rel', string='表面工艺', compute='_compute_model_process_parameters_ids' , store=True @@ -76,7 +78,11 @@ class SfDemandPlan(models.Model): ('4', '低'), ], string='优先级', default='3') - remark = fields.Char('备注') + 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): @@ -134,3 +140,41 @@ class SfDemandPlan(models.Model): 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 != '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' + elif len(line.line_ids) == len(status_line): + line.state = '40' + elif bool(status_line): + line.state = '30' + else: + line.state = '10' diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index 6c745f3b..99b1d1ef 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -5,7 +5,6 @@ from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from odoo.tools import float_compare from datetime import datetime, timedelta -from odoo.exceptions import UserError class SfProductionDemandPlan(models.Model): @@ -22,7 +21,7 @@ class SfProductionDemandPlan(models.Model): ('2', '高'), ('3', '中'), ('4', '低'), - ], string='优先级', default='3') + ], string='优先级', related='demand_plan_id.priority') status = fields.Selection([ ('10', '草稿'), ('20', '待确认'), @@ -30,7 +29,7 @@ class SfProductionDemandPlan(models.Model): ('50', '待下达生产'), ('60', '已下达'), ('100', '取消'), - ], string='状态', compute='_compute_status', store=True) + ], string='状态', compute='_compute_status', store=True, readonly=False) demand_plan_id = fields.Many2one(comodel_name="sf.demand.plan", string="物料需求", readonly=True) sale_order_id = fields.Many2one(comodel_name="sale.order", @@ -58,7 +57,7 @@ class SfProductionDemandPlan(models.Model): ('manual', "人工线下加工"), ('purchase', "外购"), ('outsourcing', "委外加工"), - ], string='供货方式', related='sale_order_line_id.supply_method', store=True) + ], string='供货方式') product_uom_qty = fields.Float( string="需求数量", related='sale_order_line_id.product_uom_qty', store=True) @@ -78,12 +77,8 @@ class SfProductionDemandPlan(models.Model): 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='表面工艺', - related='demand_plan_id.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( @@ -137,6 +132,16 @@ class SfProductionDemandPlan(models.Model): outsourcing_purchase_request = fields.Char('委外采购申请单') + plan_uom_qty = fields.Float(string="计划量") + procurement_reason = fields.Selection([ + ('销售订单', "销售订单"), + ('需求预测', "需求预测"), + ('生产报废', "生产报废"), + ], string='补货原因', default='销售订单') + + blank_arrival_date = fields.Date('采购计划到货(坯料)') + finished_product_arrival_date = fields.Date('采购计划到货(成品)') + @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): @@ -271,14 +276,18 @@ class SfProductionDemandPlan(models.Model): self._do_production_schedule(pro_plan_list) def edit_button(self): - return { - 'res_model': 'sf.demand.plan', - 'type': 'ir.actions.act_window', + self.ensure_one() + action = { 'name': _("需求计划"), - 'domain': [('line_ids', 'in', self.demand_plan_id.ids)], - 'views': [[self.env.ref('sf_demand_plan.view_sf_demand_plan_list').id, 'list']], - 'target': 'new', + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'views': [[self.env.ref('sf_demand_plan.view_sf_demand_plan_form').id, 'form']], + 'res_model': 'sf.demand.plan', + 'res_id': self.demand_plan_id.id, } + if self.demand_plan_id.state == '40': + action.update({'flags': {'mode': 'readonly'}}) + return action def _do_production_schedule(self, pro_plan_list): for pro_plan in pro_plan_list: @@ -502,3 +511,9 @@ class SfProductionDemandPlan(models.Model): programming_no = list(set(programming_mrp_production_ids)) numbers_str = "、".join(programming_no) raise ValidationError(f"编程单号:{numbers_str},请去云平台处理") + + def button_delete(self): + self.ensure_one() + if len(self.demand_plan_id.line_ids) == 1: + raise ValidationError(f"最后一条计划,不能删除!") + self.unlink() diff --git a/sf_demand_plan/security/ir.model.access.csv b/sf_demand_plan/security/ir.model.access.csv index 106f6fc9..b588c8b6 100644 --- a/sf_demand_plan/security/ir.model.access.csv +++ b/sf_demand_plan/security/ir.model.access.csv @@ -1,6 +1,6 @@ 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 diff --git a/sf_demand_plan/views/demand_plan.xml b/sf_demand_plan/views/demand_plan.xml index b4c643ce..8dff8149 100644 --- a/sf_demand_plan/views/demand_plan.xml +++ b/sf_demand_plan/views/demand_plan.xml @@ -4,7 +4,7 @@ sf.production.demand.plan + class="demand_plan_tree" create="false" delete="false">
@@ -17,7 +20,8 @@ - + + @@ -32,6 +36,32 @@ + + + + + + + + + + + + + + + + + + +