From fb781dcf57fdf03fcb7b41f7a7003e99e2889ee3 Mon Sep 17 00:00:00 2001 From: "Tiffany Chang (tic)" Date: Mon, 22 Nov 2021 16:22:22 +0000 Subject: [PATCH] [IMP] mrp,stock: enforce consistency for MO's move values Due to many move values being set only during an onchange, there were inconsistencies with the values of the products to manufacture and the MO's components in the following cases: - when a MO has manually added components/byproducts (i.e. not from a BoM or a MO with no BoM) = missing values - a MO is copied = incorrect values copied - a backorder is made = backorders' moves contained new "-00#" name, but original "-001" MO did not update all corresponding values to new name There were also a couple of default values for manually added moves that were either incorrect or not correctly saving. Most of these inconsistencies are not visible to the user unless they do a data export or are playing close attention (e.g. copying an MTO triggered MO will copy the date_deadline, and this value cannot be edited by the user.) Part 6 of Task: 2667151 Part-of: odoo/odoo#80319 --- addons/mrp/models/mrp_production.py | 61 ++++++++++------------- addons/mrp/models/stock_move.py | 40 +++++++++++++++ addons/mrp/views/mrp_production_views.xml | 6 +-- addons/stock/models/stock_move.py | 3 +- 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 2973dc353ee..428e8ff5cba 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -228,7 +228,7 @@ class MrpProduction(models.Model): 'procurement.group', 'Procurement Group', copy=False) product_description_variants = fields.Char('Custom Description') - orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint') + orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint', copy=False) propagate_cancel = fields.Boolean( 'Propagate cancel and split', help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too') @@ -767,20 +767,6 @@ class MrpProduction(models.Model): if vals.get('date_planned_finished'): production.move_finished_ids.write({'date': production.date_planned_finished}) if any(field in ['move_raw_ids', 'move_finished_ids', 'workorder_ids'] for field in vals) and production.state != 'draft': - if production.state == 'done': - # for some reason moves added after state = 'done' won't save group_id, reference if added in - # "stock_move.default_get()" - production.move_raw_ids.filtered(lambda move: move.additional and move.date > production.date_planned_start).write({ - 'group_id': production.procurement_group_id.id, - 'reference': production.name, - 'date': production.date_planned_start, - 'date_deadline': production.date_planned_start - }) - production.move_finished_ids.filtered(lambda move: move.additional and move.date > production.date_planned_finished).write({ - 'reference': production.name, - 'date': production.date_planned_finished, - 'date_deadline': production.date_deadline - }) production._autoconfirm_production() if production in production_to_replan: production._plan_workorders(replan=True) @@ -820,12 +806,6 @@ class MrpProduction(models.Model): vals['procurement_group_id'] = self.env["procurement.group"].create(procurement_group_vals).id productions = super().create(vals_list) for production in productions: - (production.move_raw_ids | production.move_finished_ids).write({ - 'group_id': production.procurement_group_id.id, - 'origin': production.name - }) - production.move_raw_ids.write({'date': production.date_planned_start}) - production.move_finished_ids.write({'date': production.date_planned_finished}) # Trigger move_raw creation when importing a file if 'import_file' in self.env.context: production._onchange_move_raw() @@ -912,10 +892,6 @@ class MrpProduction(models.Model): move_dest_ids = self.move_dest_ids if len(group_orders) > 1: move_dest_ids |= group_orders[0].move_finished_ids.filtered(lambda m: m.product_id == self.product_id).move_dest_ids - date_planned_finished = self.date_planned_start + relativedelta(days=self.product_id.produce_delay) - date_planned_finished = date_planned_finished + relativedelta(days=self.company_id.manufacturing_lead) - if date_planned_finished == self.date_planned_start: - date_planned_finished = date_planned_finished + relativedelta(hours=1) return { 'product_id': product_id, 'product_uom_qty': product_uom_qty, @@ -923,7 +899,7 @@ class MrpProduction(models.Model): 'operation_id': operation_id, 'byproduct_id': byproduct_id, 'name': self.name, - 'date': date_planned_finished, + 'date': self._get_date_planned_finished(), 'date_deadline': self.date_deadline, 'picking_type_id': self.picking_type_id.id, 'location_id': self.product_id.with_company(self.company_id).property_stock_production.id, @@ -938,6 +914,13 @@ class MrpProduction(models.Model): 'cost_share': cost_share, } + def _get_date_planned_finished(self): + date_planned_finished = self.date_planned_start + relativedelta(days=self.product_id.produce_delay) + date_planned_finished = date_planned_finished + relativedelta(days=self.company_id.manufacturing_lead) + if date_planned_finished == self.date_planned_start: + date_planned_finished = date_planned_finished + relativedelta(hours=1) + return date_planned_finished + def _get_moves_finished_values(self): moves = [] for production in self: @@ -999,12 +982,11 @@ class MrpProduction(models.Model): return moves def _get_move_raw_values(self, product_id, product_uom_qty, product_uom, operation_id=False, bom_line=False): + """ Warning, any changes done to this method will need to be repeated for consistency in: + - Manually added components, i.e. "default_" values in view + - Moves from a copied MO, i.e. move.create + - Existing moves during backorder creation """ source_location = self.location_src_id - origin = self.name - if self.orderpoint_id: - origin = self.origin.replace( - '%s - ' % (self.orderpoint_id.display_name), '') - origin = '%s,%s' % (origin, self.name) data = { 'sequence': bom_line.sequence if bom_line else 10, 'name': self.name, @@ -1022,7 +1004,7 @@ class MrpProduction(models.Model): 'operation_id': operation_id, 'price_unit': product_id.standard_price, 'procure_method': 'make_to_stock', - 'origin': origin, + 'origin': self._get_origin(), 'state': 'draft', 'warehouse_id': source_location.warehouse_id.id, 'group_id': self.procurement_group_id.id, @@ -1030,6 +1012,14 @@ class MrpProduction(models.Model): } return data + def _get_origin(self): + origin = self.name + if self.orderpoint_id and self.origin: + origin = self.origin.replace( + '%s - ' % (self.orderpoint_id.display_name), '') + origin = '%s,%s' % (origin, self.name) + return origin + def _set_qty_producing(self): if self.product_id.tracking == 'serial': qty_producing_uom = self.product_uom_id._compute_quantity(self.qty_producing, self.product_id.uom_id, rounding_method='HALF-UP') @@ -1095,9 +1085,6 @@ class MrpProduction(models.Model): additional_moves = production.move_raw_ids.filtered( lambda move: move.state == 'draft' ) - additional_moves.write({ - 'group_id': production.procurement_group_id.id, - }) additional_moves._adjust_procure_method() moves_to_confirm |= additional_moves additional_byproducts = production.move_finished_ids.filtered( @@ -1496,6 +1483,8 @@ class MrpProduction(models.Model): 'lot_producing_id': False, 'origin': self.origin, 'state': 'confirmed', + 'date_deadline': self.date_deadline, + 'orderpoint_id': self.orderpoint_id.id, } def _split_productions(self, amounts=False, cancel_remaning_qty=False): @@ -1534,6 +1523,8 @@ class MrpProduction(models.Model): if production.backorder_sequence == 0: # Activate backorder naming production.backorder_sequence = 1 production.name = self._get_name_backorder(production.name, production.backorder_sequence) + (production.move_raw_ids | production.move_finished_ids).name = production.name + (production.move_raw_ids | production.move_finished_ids).origin = production._get_origin() production.product_qty = amounts[production][0] backorder_vals = production.copy_data(default=production._get_backorder_mo_vals())[0] backorder_qtys = amounts[production][1:] diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 65c48ce1483..726b60fffd8 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from collections import defaultdict from odoo import api, Command, fields, models from odoo.osv import expression from odoo.tools import float_compare, float_round, float_is_zero, OrderedSet @@ -242,6 +243,40 @@ class StockMove(models.Model): defaults['reference'] = production_id.name return defaults + @api.model_create_multi + def create(self, vals_list): + """ Enforce consistent values (i.e. match _get_move_raw_values/_get_move_finished_values) for: + - Manually added components/byproducts specifically values we can't set via view with "default_" + - Moves from a copied MO + - Backorders + """ + mo_id_to_mo = defaultdict(lambda: self.env['mrp.production']) + product_id_to_product = defaultdict(lambda: self.env['product.product']) + for values in vals_list: + mo_id = values.get('raw_material_production_id', False) or values.get('production_id', False) + if mo_id: + mo = mo_id_to_mo[mo_id] + if not mo: + mo = mo.browse(mo_id) + mo_id_to_mo[mo_id] = mo + values['name'] = mo.name + values['origin'] = mo._get_origin() + values['group_id'] = mo.procurement_group_id.id + values['propagate_cancel'] = mo.propagate_cancel + if values.get('raw_material_production_id', False): + product = product_id_to_product[values['product_id']] + if not product: + product = product.browse(values['product_id']) + product_id_to_product[values['product_id']] = product + values['location_dest_id'] = mo.production_location_id.id + values['price_unit'] = product.standard_price + continue + # produced products + byproducts + values['location_id'] = mo.production_location_id.id + values['date'] = mo._get_date_planned_finished() + values['date_deadline'] = mo.date_deadline + return super().create(vals_list) + def write(self, vals): if 'product_uom_qty' in vals and 'move_line_ids' in vals: # first update lines then product_uom_qty as the later will unreserve @@ -490,3 +525,8 @@ class StockMove(models.Model): self.move_line_ids = self._set_quantity_done_prepare_vals(quantity_done) else: super()._multi_line_quantity_done_set(quantity_done) + + def _prepare_extra_move_vals(self, qty): + vals = super()._prepare_extra_move_vals(qty) + vals['date_deadline'] = self.date_deadline + return vals diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 0592513303d..f5bc9847ec0 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -263,7 +263,7 @@ @@ -285,7 +285,6 @@ - @@ -344,7 +343,7 @@ - + @@ -374,6 +373,7 @@ + diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 12a60569086..745b8276dcc 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -34,7 +34,7 @@ class StockMove(models.Model): 'Date Scheduled', default=fields.Datetime.now, index=True, required=True, help="Scheduled date until move is done, then date of actual move processing") date_deadline = fields.Datetime( - "Deadline", readonly=True, + "Deadline", readonly=True, copy=False, help="Date Promise to the customer on the top level document (SO/PO)") company_id = fields.Many2one( 'res.company', 'Company', @@ -1691,6 +1691,7 @@ class StockMove(models.Model): 'move_orig_ids': [(4, x.id) for x in self.move_orig_ids], 'origin_returned_move_id': self.origin_returned_move_id.id, 'price_unit': self.price_unit, + 'date_deadline': self.date_deadline, } if self.env.context.get('force_split_uom_id'): vals['product_uom'] = self.env.context['force_split_uom_id']