From c2cb24c60b52f8eb6653649a61866562a83e0917 Mon Sep 17 00:00:00 2001 From: guanhuan Date: Wed, 9 Jul 2025 11:38:40 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E9=9C=80=E6=B1=82=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/models/sf_demand_plan.py | 3 +-- sf_demand_plan/models/sf_production_demand_plan.py | 12 ++++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/sf_demand_plan/models/sf_demand_plan.py b/sf_demand_plan/models/sf_demand_plan.py index 63371dfd..83e890af 100644 --- a/sf_demand_plan/models/sf_demand_plan.py +++ b/sf_demand_plan/models/sf_demand_plan.py @@ -146,9 +146,8 @@ class SfDemandPlan(models.Model): 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 if float_compare(pending_qty, 0, - precision_rounding=rounding) == -1: + precision_rounding=line.product_id.uom_id.rounding) == -1: line.pending_qty = 0 else: line.pending_qty = pending_qty diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index a0b5bb92..4daa7ae3 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -28,10 +28,8 @@ class SfProductionDemandPlan(models.Model): ], string='状态', default='30', readonly=True) demand_plan_id = fields.Many2one(comodel_name="sf.demand.plan", string="物料需求", readonly=True) - sale_order_id = fields.Many2one(comodel_name="sale.order", related='demand_plan_id.sale_order_id', - string="销售订单", readonly=True) - sale_order_line_id = fields.Many2one(comodel_name="sale.order.line", related='demand_plan_id.sale_order_line_id', - string="销售订单明细", 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", string="销售订单明细", readonly=True) sale_order_line_number = fields.Char(string='销售订单行', compute='_compute_sale_order_line_number', store=True) company_id = fields.Many2one( related='sale_order_id.company_id', @@ -215,9 +213,8 @@ class SfProductionDemandPlan(models.Model): @api.depends('sale_order_line_id.qty_to_deliver') def _compute_qty_to_deliver(self): for line in self: - rounding = line.product_id.uom_id.rounding or 0.01 if float_compare(line.sale_order_line_id.qty_to_deliver, 0, - precision_rounding=rounding) == -1: + precision_rounding=line.product_id.uom_id.rounding) == -1: line.qty_to_deliver = 0 else: line.qty_to_deliver = line.sale_order_line_id.qty_to_deliver @@ -287,9 +284,8 @@ class SfProductionDemandPlan(models.Model): if record.mrp_production_ids and record.mrp_production_ids.move_raw_ids: total_reserved_availability = sum( record.mrp_production_ids.mapped('move_raw_ids.reserved_availability')) - rounding = record.product_id.uom_id.rounding or 0.01 if float_compare(total_reserved_availability, record.plan_uom_qty, - precision_rounding=rounding) >= 0: + precision_rounding=record.product_id.uom_id.rounding) >= 0: record.material_check = '1' # 已齐套 else: record.material_check = '0' # 未齐套 From acb6fd42cae4f322a54ee37c7631c98a1b6cf4e7 Mon Sep 17 00:00:00 2001 From: guanhuan Date: Wed, 9 Jul 2025 16:46:46 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E9=87=87=E8=B4=AD=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E5=8A=A0=E9=9C=80=E6=B1=82=E8=AE=A1=E5=88=92=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/models/purchase_order.py | 26 ++++--- sf_demand_plan/models/purchase_request.py | 85 ++--------------------- sf_demand_plan/models/sf_demand_plan.py | 10 +++ 3 files changed, 30 insertions(+), 91 deletions(-) diff --git a/sf_demand_plan/models/purchase_order.py b/sf_demand_plan/models/purchase_order.py index 5bc07313..b9802866 100644 --- a/sf_demand_plan/models/purchase_order.py +++ b/sf_demand_plan/models/purchase_order.py @@ -5,38 +5,42 @@ 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) - def button_confirm(self): - if self.demand_plan_line_id: + if self.order_line[0].demand_plan_line_id: self = self.with_context( - demand_plan_line_id=self.demand_plan_line_id.id + demand_plan_line_id=self.order_line[0].demand_plan_line_id.id ) res = super(PurchaseOrder, self).button_confirm() return res - @api.depends('origin', 'demand_plan_line_id') + @api.depends('origin') 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': + if purchase.order_line[0].demand_plan_line_id.supply_method == 'outsourcing': purchase.purchase_type = 'outsourcing' - elif purchase.demand_plan_line_id.supply_method == 'purchase': + elif purchase.order_line[0].demand_plan_line_id.supply_method == 'purchase': purchase.purchase_type = 'outside' + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + + demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan", + string="需求计划明细", readonly=True) + @api.model def create(self, vals): - res = super(PurchaseOrder, self).create(vals) + res = super(PurchaseOrderLine, self).create(vals) if not res.demand_plan_line_id: - origin = [origin.replace(' ', '') for origin in res.origin.split(',')] + origin = [origin.replace(' ', '') for origin in res.order_id.origin.split(',')] if self.env.context.get('demand_plan_line_id'): res.demand_plan_line_id = self.env.context.get('demand_plan_line_id') - elif 'MO' in res.origin: + elif 'MO' in res.order_id.origin: # 原单据是制造订单 mp_ids = self.env['mrp.production'].sudo().search([('name', 'in', origin)]) if mp_ids: diff --git a/sf_demand_plan/models/purchase_request.py b/sf_demand_plan/models/purchase_request.py index 9cb15518..d1539976 100644 --- a/sf_demand_plan/models/purchase_request.py +++ b/sf_demand_plan/models/purchase_request.py @@ -28,83 +28,8 @@ class PurchaseRequestLine(models.Model): 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", - } + @api.model + def _prepare_purchase_order_line(self, po, item): + ret = super(PurchaseRequestLineMakePurchaseOrder, self)._prepare_purchase_order_line(po, item) + ret['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id + return ret diff --git a/sf_demand_plan/models/sf_demand_plan.py b/sf_demand_plan/models/sf_demand_plan.py index 83e890af..864f8002 100644 --- a/sf_demand_plan/models/sf_demand_plan.py +++ b/sf_demand_plan/models/sf_demand_plan.py @@ -184,6 +184,16 @@ class SfDemandPlan(models.Model): lambda p: p.status in ('50', '60') and p.new_supply_method == 'custom_made') line.readonly_custom_made_type = bool(production_demand_plan) + 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: From d02babaf0aaf54a708a0204abd0aa457d858de53 Mon Sep 17 00:00:00 2001 From: guanhuan Date: Wed, 9 Jul 2025 16:57:20 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E7=94=9F=E6=88=90=E7=9A=84bom=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E6=97=B6=E9=97=B4=E5=B0=918=E5=B0=8F=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/models/sf_demand_plan.py | 2 +- sf_demand_plan/models/sf_production_demand_plan.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sf_demand_plan/models/sf_demand_plan.py b/sf_demand_plan/models/sf_demand_plan.py index 864f8002..c873dd7b 100644 --- a/sf_demand_plan/models/sf_demand_plan.py +++ b/sf_demand_plan/models/sf_demand_plan.py @@ -197,7 +197,7 @@ class SfDemandPlan(models.Model): def name_get(self): result = [] for plan in self: - result.append((plan.id, plan.sale_order_id.name)) + result.append((plan.id, plan.product_id.name)) return result def button_production_release_plan(self): diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index 4daa7ae3..4428acce 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -686,9 +686,9 @@ class SfProductionDemandPlan(models.Model): # 复制成品模板上的属性 self.product_id.product_tmpl_id.copy_template(product_template_id) - + future_time = datetime.now() + timedelta(hours=8) # 生成BOM单据编码 - code = f"{self.product_id.default_code}-{bom_type}-{datetime.now().strftime('%Y%m%d%H%M%S')}" + code = f"{self.product_id.default_code}-{bom_type}-{future_time.strftime('%Y%m%d%H%M%S')}" order_id = self.sale_order_id product = self.product_id From 4d2ab82645f6aee54aa4d857206830b3ea30cb85 Mon Sep 17 00:00:00 2001 From: guanhuan Date: Thu, 10 Jul 2025 08:57:38 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=A0=B9=E6=8D=AE=E4=BE=9B=E8=B4=A7=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E6=9D=A5=E8=B5=8B=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_dlm/models/stock_rule_inherit.py | 88 ++++++++++++++--------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/sf_dlm/models/stock_rule_inherit.py b/sf_dlm/models/stock_rule_inherit.py index 4d715196..0051a42f 100644 --- a/sf_dlm/models/stock_rule_inherit.py +++ b/sf_dlm/models/stock_rule_inherit.py @@ -6,50 +6,50 @@ from odoo import models, fields, api, _ class StockRuleInherit(models.Model): _inherit = 'stock.rule' - @api.model - def _run_buy(self, procurements): - # 判断补货组的采购类型 - procurements_group = {'standard': [], 'outsourcing': []} - for procurement, rule in procurements: - is_outsourcing = False - product = procurement.product_id - # 获取主 BOM - bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) - - if bom: - # 遍历 BOM 中的组件(即坯料等) - for line in bom.bom_line_ids: - raw_material = line.product_id - # 检查路线 - for route in raw_material.route_ids: - # print('route.name:', route.name) - if route.name == '按订单补给外包商': - is_outsourcing = True - - if is_outsourcing: - procurements_group['outsourcing'].append((procurement, rule)) - else: - procurements_group['standard'].append((procurement, rule)) - - for key, value in procurements_group.items(): - super(StockRuleInherit, self)._run_buy(value) - - if key == 'outsourcing': - for procurement, rule in value: - supplier = procurement.values.get('supplier') - if supplier: - domain = rule._make_po_get_domain(procurement.company_id, procurement.values, - supplier.partner_id) - logging.info("domain=============: %s", domain) - po = self.env['purchase.order'].sudo().search([ - ('partner_id', '=', supplier.partner_id.id), - ('company_id', '=', procurement.company_id.id), # 保证公司一致 - ('origin', 'like', procurement.origin), # 根据来源匹配 - ('state', '=', 'draft') # 状态为草稿 - ], limit=1) - logging.info("po=: %s", po) - if po: - po.write({'purchase_type': 'outsourcing'}) + # @api.model + # def _run_buy(self, procurements): + # # 判断补货组的采购类型 + # procurements_group = {'standard': [], 'outsourcing': []} + # for procurement, rule in procurements: + # is_outsourcing = False + # product = procurement.product_id + # # 获取主 BOM + # bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) + # + # if bom: + # # 遍历 BOM 中的组件(即坯料等) + # for line in bom.bom_line_ids: + # raw_material = line.product_id + # # 检查路线 + # for route in raw_material.route_ids: + # # print('route.name:', route.name) + # if route.name == '按订单补给外包商': + # is_outsourcing = True + # + # if is_outsourcing: + # procurements_group['outsourcing'].append((procurement, rule)) + # else: + # procurements_group['standard'].append((procurement, rule)) + # + # for key, value in procurements_group.items(): + # super(StockRuleInherit, self)._run_buy(value) + # + # if key == 'outsourcing': + # for procurement, rule in value: + # supplier = procurement.values.get('supplier') + # if supplier: + # domain = rule._make_po_get_domain(procurement.company_id, procurement.values, + # supplier.partner_id) + # logging.info("domain=============: %s", domain) + # po = self.env['purchase.order'].sudo().search([ + # ('partner_id', '=', supplier.partner_id.id), + # ('company_id', '=', procurement.company_id.id), # 保证公司一致 + # ('origin', 'like', procurement.origin), # 根据来源匹配 + # ('state', '=', 'draft') # 状态为草稿 + # ], limit=1) + # logging.info("po=: %s", po) + # if po: + # po.write({'purchase_type': 'outsourcing'}) # # 首先调用父类的 _run_buy 方法,以保留原有逻辑 # super(StockRuleInherit, self)._run_buy(procurements) From 1f93ba3b42626896d4e053cd02e543374f628478 Mon Sep 17 00:00:00 2001 From: guanhuan Date: Thu, 10 Jul 2025 14:11:31 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=AA=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E9=9C=80=E6=B1=82=E8=AE=A1=E5=88=92=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=98=8E=E7=BB=86=E8=A1=8C=E9=97=AE=E9=A2=98,=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E5=88=97=E8=A1=A8=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_demand_plan/models/sale_order.py | 4 +- sf_demand_plan/models/sf_demand_plan.py | 10 +++- .../models/sf_production_demand_plan.py | 18 ++++---- sf_demand_plan/views/demand_plan_info.xml | 40 ++++++++++++++-- .../wizard/sf_demand_plan_print_wizard.py | 46 ++++++++----------- 5 files changed, 76 insertions(+), 42 deletions(-) diff --git a/sf_demand_plan/models/sale_order.py b/sf_demand_plan/models/sale_order.py index fe8966a6..149fadfe 100644 --- a/sf_demand_plan/models/sale_order.py +++ b/sf_demand_plan/models/sale_order.py @@ -11,7 +11,7 @@ class ReSaleOrder(models.Model): groups='mrp.group_mrp_user', store=True) demand_plan_ids = fields.Many2many(comodel_name="sf.demand.plan", - string="需求计划", readonly=True) + string="需求计划", readonly=True) demand_plan_count = fields.Integer( string="需求计划生成计数", @@ -47,9 +47,9 @@ class ReSaleOrder(models.Model): if demand_plan.product_id.machining_drawings_name: filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] wizard_vals = { - 'demand_plan_id': demand_plan.id, 'model_id': demand_plan.model_id, 'filename_url': filename_url, + 'machining_drawings': product.machining_drawings, 'type': '1', } self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) diff --git a/sf_demand_plan/models/sf_demand_plan.py b/sf_demand_plan/models/sf_demand_plan.py index c873dd7b..7707eec3 100644 --- a/sf_demand_plan/models/sf_demand_plan.py +++ b/sf_demand_plan/models/sf_demand_plan.py @@ -167,7 +167,9 @@ class SfDemandPlan(models.Model): 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': + 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): @@ -184,6 +186,12 @@ class SfDemandPlan(models.Model): 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: diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index 4428acce..9f5d17f4 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -56,7 +56,7 @@ class SfProductionDemandPlan(models.Model): custom_made_type = fields.Selection([ ('automation', "自动化产线加工"), ('manual', "人工线下加工"), - ], string='自制类型', compute='_compute_custom_made_type', store=True) + ], string='产线类型', compute='_compute_custom_made_type', store=True) supply_method = fields.Selection([ ('automation', "自动化产线加工"), @@ -360,11 +360,12 @@ class SfProductionDemandPlan(models.Model): return action def button_action_print(self): + model_id = self.mapped('model_id') return { 'res_model': 'sf.demand.plan.print.wizard', 'type': 'ir.actions.act_window', 'name': _("打印"), - 'domain': [('demand_plan_id', 'in', self.ids)], + 'domain': [('model_id', 'in', model_id)], 'views': [[self.env.ref('sf_demand_plan.action_plan_print_tree').id, 'list']], 'target': 'new', } @@ -577,12 +578,13 @@ 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() + @api.model + def unlink(self): + for item in self: + if item.status not in ('10', '20', '30'): + raise ValidationError(u'只能删除状态为【草稿,待确认,需求确认】的需求计划。') + else: + super(SfProductionDemandPlan, item).unlink() def button_batch_release_plan(self): filtered_plan = self.filtered(lambda mo: mo.status == '30') diff --git a/sf_demand_plan/views/demand_plan_info.xml b/sf_demand_plan/views/demand_plan_info.xml index 74d9f213..13718b51 100644 --- a/sf_demand_plan/views/demand_plan_info.xml +++ b/sf_demand_plan/views/demand_plan_info.xml @@ -39,8 +39,8 @@ - - + + @@ -51,6 +51,7 @@ + @@ -69,10 +70,39 @@ class="btn-primary" attrs="{'invisible': [('hide_release_production_order', '=', False)]}" /> -