diff --git a/addons/mrp/models/mrp_abstract_workorder.py b/addons/mrp/models/mrp_abstract_workorder.py index 7e05d187f5a..c4e156ec4bb 100644 --- a/addons/mrp/models/mrp_abstract_workorder.py +++ b/addons/mrp/models/mrp_abstract_workorder.py @@ -75,18 +75,24 @@ class MrpAbstractWorkorder(models.AbstractModel): # Remove or lower quantity on exisiting workorder lines if float_compare(qty_todo, 0.0, precision_rounding=rounding) < 0: qty_todo = abs(qty_todo) - for workorder_line in move_workorder_lines.sorted(key=lambda wl: wl.qty_reserved): + # Try to decrease or remove lines that are not reserved and + # partialy reserved first. A different decrease strategy could + # be define in _unreserve_order method. + for workorder_line in move_workorder_lines.sorted(key=lambda wl: wl._unreserve_order()): if float_compare(qty_todo, 0, precision_rounding=rounding) <= 0: break + # If the quantity to consume on the line is lower than the + # quantity to remove, the line could be remove. if float_compare(workorder_line.qty_to_consume, qty_todo, precision_rounding=rounding) <= 0: - # update qty_todo for next wo_line qty_todo = float_round(qty_todo - workorder_line.qty_to_consume, precision_rounding=rounding) if line_values['to_delete']: line_values['to_delete'] |= workorder_line else: line_values['to_delete'] = workorder_line + # decrease the quantity on the line else: new_val = workorder_line.qty_to_consume - qty_todo + # avoid to write a negative reserved quantity new_reserved = max(0, workorder_line.qty_reserved - qty_todo) line_values['to_update'][workorder_line] = { 'qty_to_consume': new_val, @@ -97,12 +103,17 @@ class MrpAbstractWorkorder(models.AbstractModel): else: # Search among wo lines which one could be updated qty_reserved_wl = defaultdict(float) + # Try to update the line with the greater reservation first in + # order to promote bigger batch. for workorder_line in move_workorder_lines.sorted(key=lambda wl: wl.qty_reserved, reverse=True): rounding = workorder_line.product_uom_id.rounding if float_compare(qty_todo, 0, precision_rounding=rounding) <= 0: break move_lines = workorder_line._get_move_lines() qty_reserved_wl[workorder_line.lot_id] += workorder_line.qty_reserved + # The reserved quantity according to exisiting move line + # already produced (with qty_done set) and other production + # lines with the same lot that are currently on production. qty_reserved_remaining = sum(move_lines.mapped('product_uom_qty')) - sum(move_lines.mapped('qty_done')) - qty_reserved_wl[workorder_line.lot_id] if float_compare(qty_reserved_remaining, 0, precision_rounding=rounding) > 0: qty_to_add = min(qty_reserved_remaining, qty_todo) @@ -114,6 +125,13 @@ class MrpAbstractWorkorder(models.AbstractModel): qty_todo -= qty_to_add qty_reserved_wl[workorder_line.lot_id] += qty_to_add + # If a line exists without reservation and without lot. It + # means that previous operations could not find any reserved + # quantity and created a line without lot prefilled. In this + # case, the system will not find an existing move line with + # available reservation anymore and will increase this line + # instead of creating a new line without lot and reserved + # quantities. if not workorder_line.qty_reserved and not workorder_line.lot_id and workorder_line.product_tracking != 'serial': line_values['to_update'][workorder_line] = { 'qty_done': workorder_line.qty_to_consume + qty_todo, @@ -130,9 +148,10 @@ class MrpAbstractWorkorder(models.AbstractModel): @api.model def _generate_lines_values(self, move, qty_to_consume): """ Create workorder line. First generate line based on the reservation, - in order to match the reservation. If the quantity to consume is greater - than the reservation quantity then create line with the correct quantity - to consume but without lot or serial number. + in order to prefill reserved quantity, lot and serial number. + If the quantity to consume is greater than the reservation quantity then + create line with the correct quantity to consume but without lot or + serial number. """ lines = [] is_tracked = move.product_id.tracking != 'none' @@ -227,8 +246,9 @@ class MrpAbstractWorkorder(models.AbstractModel): ) def _update_raw_moves(self): - """ Once the production is done, the lots written on workorder lines - are saved on stock move lines""" + """ Once the production is done. Modify the workorder lines into + stock move line with the registered lot and quantity done. + """ # Before writting produce quantities, we ensure they respect the bom strictness self._strict_consumption_check() vals_list = [] @@ -389,6 +409,11 @@ class MrpAbstractWorkorderLine(models.AbstractModel): return vals_list + def _unreserve_order(self): + """ Unreserve line with lower reserved quantity first """ + self.ensure_one() + return (self.qty_reserved,) + def _get_move_lines(self): return self.move_id.move_line_ids.filtered(lambda ml: ml.lot_id == self.lot_id and ml.product_id == self.product_id) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index de09754e2da..e50ba0a7e0e 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -592,6 +592,7 @@ class MrpProduction(models.Model): def action_assign(self): for production in self: production.move_raw_ids._action_assign() + production.workorder_ids._refresh_wo_lines() return True @api.multi @@ -729,7 +730,7 @@ class MrpProduction(models.Model): moves_raw.mapped('move_line_ids').write({'workorder_id': workorder.id}) (moves_finished + moves_raw).write({'workorder_id': workorder.id}) - workorder.generate_wo_lines() + workorder._generate_wo_lines() return workorders def _check_lots(self): diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 4df8c4611e4..6384b5a78ff 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -220,7 +220,7 @@ class MrpWorkorder(models.Model): raise UserError(_('The planned end date of the work order cannot be prior to the planned start date, please correct this to save the work order.')) return super(MrpWorkorder, self).write(values) - def generate_wo_lines(self): + def _generate_wo_lines(self): """ Generate workorder line """ self.ensure_one() raw_moves = self.move_raw_ids.filtered( @@ -247,6 +247,32 @@ class MrpWorkorder(models.Model): for line, vals in line_values['to_update'].items(): line.write(vals) + def _refresh_wo_lines(self): + for workorder in self: + raw_moves = workorder.move_raw_ids.filtered( + lambda move: move.state not in ('done', 'cancel') + ) + wl_to_unlink = self.env['mrp.workorder.line'] + for move in raw_moves: + rounding = move.product_uom.rounding + qty_already_consumed = 0.0 + workorder_lines = workorder.workorder_line_ids.filtered(lambda w: w.move_id == move) + for wl in workorder_lines: + if not wl.qty_done: + wl_to_unlink |= wl + continue + + qty_already_consumed += wl.qty_done + qty_to_consume = move.product_uom._compute_quantity( + workorder.qty_producing * move.unit_factor, + move.product_id.uom_id, + round=False + ) + wl_to_unlink.unlink() + if float_compare(qty_to_consume, qty_already_consumed, precision_rounding=rounding) > 0: + line_values = workorder._generate_lines_values(move, qty_to_consume - qty_already_consumed) + workorder.workorder_line_ids |= self.env['mrp.workorder.line'].create(line_values) + def _defaults_from_finished_workorder_line(self, reference_lot_lines): for r_line in reference_lot_lines: # see which lot we could suggest and its related qty_producing