diff --git a/addons/mrp/data/mrp_demo.xml b/addons/mrp/data/mrp_demo.xml index 33cc801b9fc..b15716264b4 100644 --- a/addons/mrp/data/mrp_demo.xml +++ b/addons/mrp/data/mrp_demo.xml @@ -232,7 +232,7 @@ E-COM92 - + diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 1f574c1d24e..59bafc27487 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -549,8 +549,10 @@ class MrpProduction(models.Model): consume_move_lines = moves_to_do.mapped('active_move_line_ids') for moveline in moves_to_finish.mapped('active_move_line_ids'): if moveline.move_id.has_tracking != 'none': + if any([not ml.lot_produced_id for ml in consume_move_lines]): + raise UserError(_('You can not consume without telling for which lot you consumed it')) # Link all movelines in the consumed with same lot_produced_id false or the correct lot_produced_id - filtered_lines = consume_move_lines.filtered(lambda x: x.lot_produced_id == moveline.lot_id or not x.lot_produced_id) + filtered_lines = consume_move_lines.filtered(lambda x: x.lot_produced_id == moveline.lot_id) moveline.write({'consume_line_ids': [(6, 0, [x for x in filtered_lines.ids])]}) else: # Link with everything diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 18329432edd..61e7865d5cc 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -294,11 +294,15 @@ class MrpWorkorder(models.Model): # For each untracked component without any 'temporary' move lines, # (the new workorder tablet view allows registering consumed quantities for untracked components) # we assume that only the theoretical quantity was used - raw_moves = self.move_raw_ids.filtered(lambda x: (x.has_tracking == 'none') and (x.state not in ('done', 'cancel')) and x.bom_line_id) - for move in raw_moves: - if move.unit_factor and not move.move_line_ids.filtered(lambda m: not m.done_wo): + for move in self.move_raw_ids: + if move.has_tracking == 'none' and (move.state not in ('done', 'cancel')) and move.bom_line_id\ + and move.unit_factor and not move.move_line_ids.filtered(lambda ml: not ml.done_wo): rounding = move.product_uom.rounding - move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) + if self.product_id.tracking != 'none': + qty_to_add = float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) + move._generate_consumed_move_line(qty_to_add, self.final_lot_id) + else: + move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) # Transfer quantities from temporary to final move lots or make them final for move_line in self.active_move_line_ids: diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 966e3365b6c..4eb94b2379a 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -68,6 +68,7 @@ class StockMove(models.Model): help='Technical Field to order moves') needs_lots = fields.Boolean('Tracking', compute='_compute_needs_lots') order_finished_lot_ids = fields.Many2many('stock.production.lot', compute='_compute_order_finished_lot_ids') + finished_lots_exist = fields.Boolean('Finished Lots Exist', compute='_compute_order_finished_lot_ids') @api.depends('active_move_line_ids.qty_done', 'active_move_line_ids.product_uom_id') def _compute_done_quantity(self): @@ -76,8 +77,13 @@ class StockMove(models.Model): @api.depends('raw_material_production_id.move_finished_ids.move_line_ids.lot_id') def _compute_order_finished_lot_ids(self): for move in self: - if move.product_id.tracking != 'none' and move.raw_material_production_id: - move.order_finished_lot_ids = move.raw_material_production_id.move_finished_ids.mapped('move_line_ids.lot_id').ids + if move.raw_material_production_id.move_finished_ids: + finished_lots_ids = move.raw_material_production_id.move_finished_ids.mapped('move_line_ids.lot_id').ids + if finished_lots_ids: + move.order_finished_lot_ids = finished_lots_ids + move.finished_lots_exist = True + else: + move.finished_lots_exist = False @api.depends('product_id.tracking') def _compute_needs_lots(self): @@ -178,6 +184,37 @@ class StockMove(models.Model): }) return self.env['stock.move'] + def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False): + if lot: + ml = self.move_line_ids.filtered(lambda ml: ml.lot_id == lot and not ml.lot_produced_id) + else: + ml = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_id) + if ml: + new_quantity_done = (ml.qty_done + qty_to_add) + if new_quantity_done >= ml.product_uom_qty: + ml.write({'qty_done': new_quantity_done, 'lot_produced_id': final_lot.id}) + else: + new_qty_reserved = ml.product_uom_qty - new_quantity_done + default = {'product_uom_qty': new_quantity_done, + 'qty_done': new_quantity_done, + 'lot_produced_id': final_lot.id} + ml.copy(default=default) + ml.with_context(bypass_reservation_update=True).write({'product_uom_qty': new_qty_reserved, 'qty_done': 0}) + else: + vals = { + 'move_id': self.id, + 'product_id': self.product_id.id, + 'location_id': self.location_id.id, + 'location_dest_id': self.location_dest_id.id, + 'product_uom_qty': 0, + 'product_uom_id': self.product_uom.id, + 'qty_done': qty_to_add, + 'lot_produced_id': final_lot.id, + } + if lot: + vals.update({'lot_id': lot.id}) + self.env['stock.move.line'].create(vals) + class PushedFlow(models.Model): _inherit = "stock.location.path" diff --git a/addons/mrp/tests/test_unbuild.py b/addons/mrp/tests/test_unbuild.py index 8c53593ab0f..0c98b64b386 100644 --- a/addons/mrp/tests/test_unbuild.py +++ b/addons/mrp/tests/test_unbuild.py @@ -269,8 +269,6 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100, lot_id=lot_1) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5, lot_id=lot_2) mo.action_assign() - for ml in mo.move_raw_ids.mapped('move_line_ids'): - ml.qty_done = ml.product_qty produce_wizard = self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, @@ -279,6 +277,8 @@ class TestUnbuild(TestMrpCommon): 'product_qty': 5.0, 'lot_id': lot_final.id, }) + for pl in produce_wizard.produce_line_ids: + pl.qty_done = pl.qty_to_consume produce_wizard.do_produce() mo.button_mark_done() @@ -399,3 +399,57 @@ class TestUnbuild(TestMrpCommon): self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_1), 1, 'You should have get your product with lot 1 in stock') self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 3, 'You should have the 3 basic product for lot 2 in stock') self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_3), 2, 'You should have get one product back for lot 3') + + + def test_production_links_with_non_tracked_lots(self): + """ This test produces an MO in two times and checks that the move lines are linked in a correct way + """ + mo, bom, p_final, p1, p2 = self.generate_mo(tracking_final='lot', tracking_base_1='none', tracking_base_2='lot') + lot_1 = self.env['stock.production.lot'].create({ + 'name': 'lot_1', + 'product_id': p2.id, + }) + + self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 3, lot_id=lot_1) + lot_finished_1 = self.env['stock.production.lot'].create({ + 'name': 'lot_finished_1', + 'product_id': p_final.id, + }) + + produce_wizard = self.env['mrp.product.produce'].with_context({ + 'active_id': mo.id, + 'active_ids': [mo.id], + }).create({ + 'product_qty': 3.0, + 'lot_id': lot_finished_1.id, + }) + + produce_wizard.produce_line_ids[0].lot_id = lot_1.id + produce_wizard.do_produce() + + lot_2 = self.env['stock.production.lot'].create({ + 'name': 'lot_2', + 'product_id': p2.id, + }) + + self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 4, lot_id=lot_2) + lot_finished_2 = self.env['stock.production.lot'].create({ + 'name': 'lot_finished_2', + 'product_id': p_final.id, + }) + + produce_wizard = self.env['mrp.product.produce'].with_context({ + 'active_id': mo.id, + 'active_ids': [mo.id], + }).create({ + 'product_qty': 2.0, + 'lot_id': lot_finished_2.id, + }) + + produce_wizard.produce_line_ids[0].lot_id = lot_2.id + produce_wizard.do_produce() + mo.button_mark_done() + ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and m.lot_produced_id == lot_finished_1) + self.assertEqual(ml.qty_done, 12.0, 'Should have consumed 12 for the first lot') + ml = mo.finished_move_line_ids[1].consume_line_ids.filtered(lambda m: m.product_id == p1 and m.lot_produced_id == lot_finished_2) + self.assertEqual(ml.qty_done, 8.0, 'Should have consumed 8 for the second lot') \ No newline at end of file diff --git a/addons/mrp/views/stock_move_views.xml b/addons/mrp/views/stock_move_views.xml index 9a1e0970836..879f576f3f9 100644 --- a/addons/mrp/views/stock_move_views.xml +++ b/addons/mrp/views/stock_move_views.xml @@ -26,7 +26,7 @@