diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 3c9eb1cd267..5cdc55888ad 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -1040,6 +1040,7 @@ class MrpProduction(models.Model): 'warehouse_id': source_location.warehouse_id.id, 'group_id': self.procurement_group_id.id, 'propagate_cancel': self.propagate_cancel, + 'manual_consumption': self.env['stock.move']._determine_is_manual_consumption(product_id, self, bom_line), } return data diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 27a0b318290..1bd45236019 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -149,8 +149,7 @@ class StockMove(models.Model): for move in self: if move.state != 'draft': continue - move.manual_consumption = (move.has_tracking != 'none' and not move.raw_material_production_id.use_auto_consume_components_lots) or \ - (move.has_tracking == 'none' and move.bom_line_id.manual_consumption) + move.manual_consumption = move._is_manual_consumption() @api.depends('bom_line_id') def _compute_description_bom_line(self): @@ -444,7 +443,7 @@ class StockMove(models.Model): 'state': 'confirmed', 'reservation_date': self.reservation_date, 'date_deadline': self.date_deadline, - 'manual_consumption': self.bom_line_id.manual_consumption or self.product_id.tracking != 'none', + 'manual_consumption': self._is_manual_consumption(), 'move_orig_ids': [Command.link(m.id) for m in self.mapped('move_orig_ids')], 'move_dest_ids': [Command.link(m.id) for m in self.mapped('move_dest_ids')], 'procure_method': self.procure_method, @@ -593,3 +592,12 @@ class StockMove(models.Model): 'res_id': source.id, } return res + + def _is_manual_consumption(self): + self.ensure_one() + return self._determine_is_manual_consumption(self.product_id, self.raw_material_production_id, self.bom_line_id) + + @api.model + def _determine_is_manual_consumption(self, product, production, bom_line): + return (product.product_tmpl_id.tracking != 'none' and not production.use_auto_consume_components_lots) or \ + (product.product_tmpl_id.tracking == 'none' and bom_line and bom_line.manual_consumption) diff --git a/addons/mrp/tests/test_manual_consumption.py b/addons/mrp/tests/test_manual_consumption.py index a6858990e64..3a8496e6e13 100644 --- a/addons/mrp/tests/test_manual_consumption.py +++ b/addons/mrp/tests/test_manual_consumption.py @@ -1,6 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. # -*- coding: utf-8 -*- +from odoo.addons.mrp.tests.common import TestMrpCommon from odoo.tests import tagged, Form, HttpCase @@ -81,3 +82,108 @@ class TestTourManualConsumption(HttpCase): self.assertEqual(move_nt.manual_consumption, False) self.assertEqual(move_sn.manual_consumption, True) self.assertEqual(move_lot.manual_consumption, True) + + +class TestManualConsumption(TestMrpCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stock_location = cls.env.ref('stock.stock_location_stock') + + def test_manual_consumption_backorder_00(self): + """Test when use_auto_consume_components_lots is not set, manual consumption + of the backorder is correctly set. + """ + mo, _, _final, c1, c2 = self.generate_mo('none', 'lot', 'none', qty_final=2) + + self.assertTrue(mo.move_raw_ids.filtered(lambda m: m.product_id == c1).manual_consumption) + self.assertFalse(mo.move_raw_ids.filtered(lambda m: m.product_id == c2).manual_consumption) + + lot = self.env['stock.lot'].create({ + 'name': 'lot', + 'product_id': c1.id, + 'company_id': self.env.company.id, + }) + self.env['stock.quant']._update_available_quantity(c1, self.stock_location, 8, lot_id=lot) + self.env['stock.quant']._update_available_quantity(c2, self.stock_location, 2) + + mo.action_assign() + mo_form = Form(mo) + mo_form.qty_producing = 1 + mo_form.save() + self.assertEqual(sum(mo.move_raw_ids.filtered(lambda m: m.product_id.id == c1.id).mapped("quantity_done")), 0) + self.assertEqual(sum(mo.move_raw_ids.filtered(lambda m: m.product_id.id == c2.id).mapped("quantity_done")), 1) + + details_operation_form = Form(mo.move_raw_ids.filtered(lambda m: m.product_id == c1), view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 4 + ml.lot_id = lot + details_operation_form.save() + + action = mo.button_mark_done() + backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context'])) + backorder.save().action_backorder() + backorder_mo = mo.procurement_group_id.mrp_production_ids[-1] + + self.assertTrue(backorder_mo.move_raw_ids.filtered(lambda m: m.product_id == c1).manual_consumption) + self.assertFalse(backorder_mo.move_raw_ids.filtered(lambda m: m.product_id == c2).manual_consumption) + + def test_manual_consumption_backorder_01(self): + """Test when use_auto_consume_components_lots is set, manual consumption + of the backorder is correctly set. + """ + picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0] + picking_type.use_auto_consume_components_lots = True + + mo, _, _final, c1, c2 = self.generate_mo('none', 'lot', 'none', qty_final=2) + + self.assertFalse(mo.move_raw_ids.filtered(lambda m: m.product_id == c1).manual_consumption) + self.assertFalse(mo.move_raw_ids.filtered(lambda m: m.product_id == c2).manual_consumption) + + lot = self.env['stock.lot'].create({ + 'name': 'lot', + 'product_id': c1.id, + 'company_id': self.env.company.id, + }) + self.env['stock.quant']._update_available_quantity(c1, self.stock_location, 8, lot_id=lot) + self.env['stock.quant']._update_available_quantity(c2, self.stock_location, 2) + + mo.action_assign() + mo_form = Form(mo) + mo_form.qty_producing = 1 + mo_form.save() + self.assertEqual(sum(mo.move_raw_ids.filtered(lambda m: m.product_id.id == c1.id).mapped("quantity_done")), 4) + self.assertEqual(sum(mo.move_raw_ids.filtered(lambda m: m.product_id.id == c2.id).mapped("quantity_done")), 1) + + action = mo.button_mark_done() + backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context'])) + backorder.save().action_backorder() + backorder_mo = mo.procurement_group_id.mrp_production_ids[-1] + + self.assertFalse(backorder_mo.move_raw_ids.filtered(lambda m: m.product_id == c1).manual_consumption) + self.assertFalse(backorder_mo.move_raw_ids.filtered(lambda m: m.product_id == c2).manual_consumption) + + def test_manual_consumption_split_merge_00(self): + """Test manual consumption is correctly set after split or merge. + """ + # Change 'Units' rounding to 1 (integer only quantities) + self.uom_unit.rounding = 1 + # Create a mo for 10 products + mo, _, _, p1, p2 = self.generate_mo('none', 'lot', 'none', qty_final=10) + self.assertTrue(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).manual_consumption) + self.assertFalse(mo.move_raw_ids.filtered(lambda m: m.product_id == p2).manual_consumption) + + # Split in 3 parts + action = mo.action_split() + wizard = Form(self.env[action['res_model']].with_context(action['context'])) + wizard.counter = 3 + action = wizard.save().action_split() + for production in mo.procurement_group_id.mrp_production_ids: + self.assertTrue(production.move_raw_ids.filtered(lambda m: m.product_id == p1).manual_consumption) + self.assertFalse(production.move_raw_ids.filtered(lambda m: m.product_id == p2).manual_consumption) + + # Merge them back + action = mo.procurement_group_id.mrp_production_ids.action_merge() + mo = self.env[action['res_model']].browse(action['res_id']) + self.assertTrue(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).manual_consumption) + self.assertFalse(mo.move_raw_ids.filtered(lambda m: m.product_id == p2).manual_consumption)