From cd288bd1d9b14e15fc462efe63c909d41d652c5c Mon Sep 17 00:00:00 2001 From: fw-bot Date: Mon, 28 Sep 2020 10:23:31 +0000 Subject: [PATCH] [IMP] stock, mrp: make _split() return move values This commit change the _split method to return new move values instead of create it and confirm it. This allow multi creation and multi confirmation when every move of the recordset are split Task : 2076873 closes odoo/odoo#58594 Signed-off-by: William Henrotin --- addons/mrp/models/mrp_production.py | 26 ++++++++++++++++++++------ addons/mrp/models/stock_move.py | 7 ------- addons/stock/models/stock_move.py | 29 +++++++++++++---------------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 3c5cd1d7d42..3f7bf66f4e6 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -1367,16 +1367,19 @@ class MrpProduction(models.Model): 'production_id': backorder_mo.id, }) else: + new_moves_vals = [] for move in production.move_raw_ids | production.move_finished_ids: if not move.additional: qty_to_split = move.product_uom_qty - move.unit_factor * production.qty_producing - new_move = self.env['stock.move'].browse(move._split(qty_to_split)) + move_vals = move._split(qty_to_split) + if not move_vals: + continue if move.raw_material_production_id: - new_move.raw_material_production_id = backorder_mo.id + move_vals[0]['raw_material_production_id'] = backorder_mo.id else: - new_move.production_id = backorder_mo.id - (move | new_move)._do_unreserve() - (move | new_move)._action_assign() + move_vals[0]['production_id'] = backorder_mo.id + new_moves_vals.append(move_vals[0]) + new_moves = self.env['stock.move'].create(new_moves_vals) backorders |= backorder_mo for wo in backorder_mo.workorder_ids: wo.qty_produced = 0 @@ -1395,8 +1398,14 @@ class MrpProduction(models.Model): workorder.duration_expected = workorder.duration_expected * ratio for workorder in backorder_mo.workorder_ids: workorder.duration_expected = workorder.duration_expected * (1 - ratio) + + # As we have split the moves before validating them, we need to 'remove' the excess reservation + if not close_mo: + self.move_raw_ids.filtered(lambda m: not m.additional)._do_unreserve() + self.move_raw_ids.filtered(lambda m: not m.additional)._action_assign() # Confirm only productions with remaining components backorders.filtered(lambda mo: mo.move_raw_ids).action_confirm() + backorders.filtered(lambda mo: mo.move_raw_ids).action_assign() # Remove the serial move line without reserved quantity. Post inventory will assigned all the non done moves # So those move lines are duplicated. @@ -1440,7 +1449,12 @@ class MrpProduction(models.Model): }) for production in self: - production.write({'date_finished': fields.Datetime.now(), 'product_qty': production.qty_produced, 'priority': '0'}) + production.write({ + 'date_finished': fields.Datetime.now(), + 'product_qty': production.qty_produced, + 'priority': '0', + 'is_locked': True, + }) for workorder in self.workorder_ids.filtered(lambda w: w.state not in ('done', 'cancel')): workorder.duration_expected = workorder._get_duration_expected() diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 044c6208fc9..c2a61b97090 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -109,13 +109,6 @@ class StockMove(models.Model): order_finished_lot_ids = fields.Many2many('stock.production.lot', string="Finished Lot/Serial Number", compute='_compute_order_finished_lot_ids') should_consume_qty = fields.Float('Quantity To Consume', compute='_compute_should_consume_qty', digits='Product Unit of Measure') - def _unreserve_initial_demand(self, new_move): - # If you were already putting stock.move.lots on the next one in the work order, transfer those to the new move - self.filtered(lambda m: m.production_id or m.raw_material_production_id)\ - .mapped('move_line_ids')\ - .filtered(lambda ml: ml.qty_done == 0.0)\ - .write({'move_id': new_move, 'product_uom_qty': 0}) - @api.depends('raw_material_production_id.priority') def _compute_priority(self): super()._compute_priority() diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index a4e053b9765..a9667bad86a 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1442,9 +1442,6 @@ class StockMove(models.Model): break return extra_move | self - def _unreserve_initial_demand(self, new_move): - pass - def _action_done(self, cancel_backorder=False): self.filtered(lambda move: move.state == 'draft')._action_confirm() # MRP allows scrapping draft moves moves = self.exists().filtered(lambda x: x.state not in ('done', 'cancel')) @@ -1466,6 +1463,7 @@ class StockMove(models.Model): moves_todo._check_company() # Split moves where necessary and move quants + backorder_moves_vals = [] for move in moves_todo: # To know whether we need to create a backorder or not, round to the general product's # decimal precision and not the product's UOM. @@ -1473,10 +1471,12 @@ class StockMove(models.Model): if float_compare(move.quantity_done, move.product_uom_qty, precision_digits=rounding) < 0: # Need to do some kind of conversion here qty_split = move.product_uom._compute_quantity(move.product_uom_qty - move.quantity_done, move.product_id.uom_id, rounding_method='HALF-UP') - new_move = move._split(qty_split) - move._unreserve_initial_demand(new_move) - if cancel_backorder: - self.env['stock.move'].browse(new_move).with_context(moves_todo=moves_todo)._action_cancel() + new_move_vals = move._split(qty_split) + backorder_moves_vals += new_move_vals + backorder_moves = self.env['stock.move'].create(backorder_moves_vals) + backorder_moves._action_confirm(merge=False) + if cancel_backorder: + backorder_moves.with_context(moves_todo=moves_todo)._action_cancel() moves_todo.mapped('move_line_ids').sorted()._action_done() # Check the consistency of the result packages; there should be an unique location across # the contained quants. @@ -1524,13 +1524,12 @@ class StockMove(models.Model): return vals def _split(self, qty, restrict_partner_id=False): - """ Splits qty from move move into a new move + """ Splits `self` quantity and return values for a new moves to be created afterwards :param qty: float. quantity to split (given in product UoM) :param restrict_partner_id: optional partner that can be given in order to force the new move to restrict its choice of quants to the ones belonging to this partner. - :param context: dictionay. can contains the special key 'source_location_id' in order to force the source location when copying the move - :returns: id of the backorder move created """ - self = self.with_prefetch() # This makes the ORM only look for one record and not 300 at a time, which improves performance + :returns: list of dict. stock move values """ + self.ensure_one() if self.state in ('done', 'cancel'): raise UserError(_('You cannot split a stock move that has been set to \'Done\'.')) elif self.state == 'draft': @@ -1538,7 +1537,7 @@ class StockMove(models.Model): # case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode. raise UserError(_('You cannot split a draft move. It needs to be confirmed first.')) if float_is_zero(qty, precision_rounding=self.product_id.uom_id.rounding) or self.product_qty <= qty: - return self.id + return [] decimal_precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') @@ -1558,17 +1557,15 @@ class StockMove(models.Model): # TDE CLEANME: remove context key + add as parameter if self.env.context.get('source_location_id'): defaults['location_id'] = self.env.context['source_location_id'] - new_move = self.copy(defaults) + new_move_vals = self.copy_data(defaults) - # FIXME: pim fix your crap # Update the original `product_qty` of the move. Use the general product's decimal # precision and not the move's UOM to handle case where the `quantity_done` is not # compatible with the move's UOM. new_product_qty = self.product_id.uom_id._compute_quantity(self.product_qty - qty, self.product_uom, round=False) new_product_qty = float_round(new_product_qty, precision_digits=self.env['decimal.precision'].precision_get('Product Unit of Measure')) self.with_context(do_not_unreserve=True).write({'product_uom_qty': new_product_qty}) - new_move = new_move._action_confirm(merge=False) - return new_move.id + return new_move_vals def _recompute_state(self): for move in self: