From da8c905428fceb8ae654398cc85e42f2889d14cc Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Tue, 14 Mar 2023 09:07:11 +0000 Subject: [PATCH] [IMP] mrp: clean test files + adapt to code No need a common file for 3 tests. On top of it contains global variable and a lot of indirection, making it impossible to read. Also before the quant reservation commit, a savepoint was call during the action_assign and trigger a flush and recompute all. Since it has been remove, it's computed during the first read on it, that's during the mark as done and since the MO state is not draft it will keep the default value (False). Call it after create to trigger the computation at the right time. + a bit of linting closes odoo/odoo#115328 Related: odoo/enterprise#38207 Signed-off-by: William Henrotin (whe) --- addons/mrp/tests/__init__.py | 2 +- .../tests/common_consume_tracked_component.py | 233 ----------- addons/mrp/tests/test_consume_component.py | 385 ++++++++++++++++++ .../tests/test_consume_tracked_component.py | 157 ------- 4 files changed, 386 insertions(+), 391 deletions(-) delete mode 100644 addons/mrp/tests/common_consume_tracked_component.py create mode 100644 addons/mrp/tests/test_consume_component.py delete mode 100644 addons/mrp/tests/test_consume_tracked_component.py diff --git a/addons/mrp/tests/__init__.py b/addons/mrp/tests/__init__.py index 0bb9a9bf327..6467e3450df 100644 --- a/addons/mrp/tests/__init__.py +++ b/addons/mrp/tests/__init__.py @@ -15,5 +15,5 @@ from . import test_multicompany from . import test_backorder from . import test_smp from . import test_performance -from . import test_consume_tracked_component +from . import test_consume_component from . import test_manual_consumption diff --git a/addons/mrp/tests/common_consume_tracked_component.py b/addons/mrp/tests/common_consume_tracked_component.py deleted file mode 100644 index e3c11fdb1af..00000000000 --- a/addons/mrp/tests/common_consume_tracked_component.py +++ /dev/null @@ -1,233 +0,0 @@ -import copy - -from odoo.exceptions import UserError -from odoo.tests import common - -class TestConsumeTrackedComponentCommon(common.TransactionCase): - - @classmethod - def setUpClass(cls): - """ - The following variables are used in each test to define the number of MO to generate. - They're also used as a verification in the executeConsumptionTriggers() to see if enough MO were passed to it - in order to test all the triggers. - - SERIAL : MO's product_tracking is 'serial' - DEFAULT : MO's product_tracking is 'none' or 'lot' - AVAILABLE : MO'S raw components are fully available - """ - super().setUpClass() - - cls.SERIAL_AVAILABLE_TRIGGERS_COUNT = 3 - cls.DEFAULT_AVAILABLE_TRIGGERS_COUNT = 2 - cls.SERIAL_TRIGGERS_COUNT = 2 - cls.DEFAULT_TRIGGERS_COUNT = 1 - - cls.manufacture_route = cls.env.ref('mrp.route_warehouse0_manufacture') - cls.stock_id = cls.env.ref('stock.stock_location_stock').id - - cls.picking_type = cls.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0] - cls.picking_type.use_create_components_lots = True - cls.picking_type.use_auto_consume_components_lots = True - - #Create Products & Components - cls.produced_lot = cls.env['product.product'].create({ - 'name': 'Produced Lot', - 'type': 'product', - 'categ_id': cls.env.ref('product.product_category_all').id, - 'tracking' : 'lot', - 'route_ids': [(4, cls.manufacture_route.id, 0)], - }) - cls.produced_serial = cls.env['product.product'].create({ - 'name': 'Produced Serial', - 'type': 'product', - 'categ_id': cls.env.ref('product.product_category_all').id, - 'tracking' : 'serial', - 'route_ids': [(4, cls.manufacture_route.id, 0)], - }) - cls.produced_none = cls.env['product.product'].create({ - 'name': 'Produced None', - 'type': 'product', - 'categ_id': cls.env.ref('product.product_category_all').id, - 'tracking' : 'none', - 'route_ids': [(4, cls.manufacture_route.id, 0)], - }) - - cls.raw_lot = cls.env['product.product'].create({ - 'name': 'Raw Lot', - 'type': 'product', - 'categ_id': cls.env.ref('product.product_category_all').id, - 'tracking' : 'lot', - }) - cls.raw_serial = cls.env['product.product'].create({ - 'name': 'Raw Serial', - 'type': 'product', - 'categ_id': cls.env.ref('product.product_category_all').id, - 'tracking' : 'serial', - }) - cls.raw_none = cls.env['product.product'].create({ - 'name': 'Raw None', - 'type': 'product', - 'categ_id': cls.env.ref('product.product_category_all').id, - 'tracking' : 'none', - }) - - cls.raws = [cls.raw_none, cls.raw_lot, cls.raw_serial] - - #Workcenter - cls.workcenter = cls.env['mrp.workcenter'].create({ - 'name' : 'Assembly Line', - }) - - #BoMs - cls.bom_none = cls.env['mrp.bom'].create({ - 'product_tmpl_id' : cls.produced_none.product_tmpl_id.id, - 'product_uom_id' : cls.produced_none.uom_id.id, - 'consumption' : 'flexible', - 'sequence' : 1 - }) - - cls.bom_none_lines = cls.create_bom_lines(cls.bom_none, cls.raws, [3, 2, 1]) - - cls.bom_lot = cls.env['mrp.bom'].create({ - 'product_tmpl_id' : cls.produced_lot.product_tmpl_id.id, - 'product_uom_id' : cls.produced_lot.uom_id.id, - 'consumption' : 'flexible', - 'sequence' : 2 - }) - - cls.bom_lot_lines = cls.create_bom_lines(cls.bom_lot, cls.raws, [3, 2, 1]) - - cls.bom_serial = cls.env['mrp.bom'].create({ - 'product_tmpl_id' : cls.produced_serial.product_tmpl_id.id, - 'product_uom_id' : cls.produced_serial.uom_id.id, - 'consumption' : 'flexible', - 'sequence' : 1 - }) - - cls.bom_serial_lines = cls.create_bom_lines(cls.bom_serial, cls.raws, [3, 2, 1]) - - #Manufacturing Orders - cls.mo_none_tmpl = { - 'product_id' : cls.produced_none.id, - 'product_uom_id' : cls.produced_none.uom_id.id, - 'product_qty' : 1, - 'bom_id' : cls.bom_none.id - } - - cls.mo_lot_tmpl = { - 'product_id' : cls.produced_lot.id, - 'product_uom_id' : cls.produced_lot.uom_id.id, - 'product_qty' : 1, - 'bom_id' : cls.bom_lot.id - } - - cls.mo_serial_tmpl = { - 'product_id' : cls.produced_serial.id, - 'product_uom_id' : cls.produced_serial.uom_id.id, - 'product_qty' : 1, - 'bom_id' : cls.bom_serial.id - } - - @classmethod - def create_quant(cls, product, qty, offset=0, name="L"): - i = 1 - if product.tracking == 'serial': - i, qty = qty, 1 - if name == "L": - name = "S" - - vals = [] - for x in range(1, i+1): - qDict = { - 'location_id': cls.stock_id, - 'product_id': product.id, - 'inventory_quantity': qty, - } - - if product.tracking != 'none': - qDict['lot_id'] = cls.env['stock.lot'].create({ - 'name': name + str(offset + x), - 'product_id': product.id, - 'company_id': cls.env.company.id - }).id - vals.append(qDict) - - return cls.env['stock.quant'].create(vals) - - @classmethod - def create_bom_lines(cls, bom, products, quantities=None): - if quantities is None: - quantities = [1 for i in range(len(products))] - - vals = [] - for product, seq in zip(products, range(len(products))): - vals.append({ - 'product_id' : product.id, - 'product_qty' : quantities[seq], - 'product_uom_id' : product.uom_id.id, - 'sequence' : seq, - 'bom_id' : bom.id, - }) - - return cls.env['mrp.bom.line'].create(vals) - - @classmethod - def create_mo(cls, template, count): - vals = [] - for _ in range(count): - vals.append(copy.deepcopy(template)) - return cls.env['mrp.production'].create(vals) - - def executeConsumptionTriggers(self, mrp_productions): - """ - There's 3 different triggers to test : _onchange_producing(), action_generate_serial(), button_mark_done(). - - Depending on the tracking of the final product and the availability of the components, - only a part of these 3 triggers is available or intended to work. - - This function automatically call and process the appropriate triggers. - """ - tracking = mrp_productions[0].product_tracking - sameTracking = True - for mo in mrp_productions: - sameTracking = sameTracking and mo.product_tracking == tracking - self.assertTrue(sameTracking, "MOs passed to the executeConsumptionTriggers method shall have the same product_tracking") - - isSerial = tracking == 'serial' - isAvailable = all(move.state == 'assigned' for move in mrp_productions.move_raw_ids) - isComponentTracking = any(move.has_tracking != 'none' for move in mrp_productions.move_raw_ids) - - countOk = True - length = len(mrp_productions) - if isSerial: - if isAvailable: - countOk = length == self.SERIAL_AVAILABLE_TRIGGERS_COUNT - else: - countOk = length == self.SERIAL_TRIGGERS_COUNT - else: - if isAvailable: - countOk = length == self.DEFAULT_AVAILABLE_TRIGGERS_COUNT - else: - countOk = length == self.DEFAULT_TRIGGERS_COUNT - self.assertTrue(countOk, "The number of MOs passed to the executeConsumptionTriggers method does not match the associated TRIGGERS_COUNT") - - mrp_productions[0].qty_producing = mrp_productions[0].product_qty - mrp_productions[0]._onchange_producing() - - i = 1 - if isSerial: - mrp_productions[i].action_generate_serial() - i += 1 - - if isAvailable: - error = False - try: - mrp_productions[i].button_mark_done() - except UserError: - error = True - - if isComponentTracking and not mrp_productions[i].picking_type_id.use_auto_consume_components_lots: - self.assertTrue(error, "Immediate Production shall raise an error when tracked product are not provided.") - else: - self.assertFalse(error, "Immediate Production shall not raise an error.") diff --git a/addons/mrp/tests/test_consume_component.py b/addons/mrp/tests/test_consume_component.py new file mode 100644 index 00000000000..31c901f7e11 --- /dev/null +++ b/addons/mrp/tests/test_consume_component.py @@ -0,0 +1,385 @@ +import copy + +from odoo.exceptions import UserError +from odoo.tests import common, tagged + + +class TestConsumeComponentCommon(common.TransactionCase): + + @classmethod + def setUpClass(cls): + """ + The following variables are used in each test to define the number of MO to generate. + They're also used as a verification in the executeConsumptionTriggers() to see if enough MO were passed to it + in order to test all the triggers. + + SERIAL : MO's product_tracking is 'serial' + DEFAULT : MO's product_tracking is 'none' or 'lot' + AVAILABLE : MO'S raw components are fully available + """ + super().setUpClass() + + cls.SERIAL_AVAILABLE_TRIGGERS_COUNT = 3 + cls.DEFAULT_AVAILABLE_TRIGGERS_COUNT = 2 + cls.SERIAL_TRIGGERS_COUNT = 2 + cls.DEFAULT_TRIGGERS_COUNT = 1 + + cls.manufacture_route = cls.env.ref('mrp.route_warehouse0_manufacture') + cls.stock_id = cls.env.ref('stock.stock_location_stock').id + + cls.picking_type = cls.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0] + cls.picking_type.use_create_components_lots = True + cls.picking_type.use_auto_consume_components_lots = True + + # Create Products & Components + cls.produced_lot = cls.env['product.product'].create({ + 'name': 'Produced Lot', + 'type': 'product', + 'categ_id': cls.env.ref('product.product_category_all').id, + 'tracking': 'lot', + 'route_ids': [(4, cls.manufacture_route.id, 0)], + }) + cls.produced_serial = cls.env['product.product'].create({ + 'name': 'Produced Serial', + 'type': 'product', + 'categ_id': cls.env.ref('product.product_category_all').id, + 'tracking': 'serial', + 'route_ids': [(4, cls.manufacture_route.id, 0)], + }) + cls.produced_none = cls.env['product.product'].create({ + 'name': 'Produced None', + 'type': 'product', + 'categ_id': cls.env.ref('product.product_category_all').id, + 'tracking': 'none', + 'route_ids': [(4, cls.manufacture_route.id, 0)], + }) + + cls.raw_lot = cls.env['product.product'].create({ + 'name': 'Raw Lot', + 'type': 'product', + 'categ_id': cls.env.ref('product.product_category_all').id, + 'tracking': 'lot', + }) + cls.raw_serial = cls.env['product.product'].create({ + 'name': 'Raw Serial', + 'type': 'product', + 'categ_id': cls.env.ref('product.product_category_all').id, + 'tracking': 'serial', + }) + cls.raw_none = cls.env['product.product'].create({ + 'name': 'Raw None', + 'type': 'product', + 'categ_id': cls.env.ref('product.product_category_all').id, + 'tracking': 'none', + }) + + cls.raws = [cls.raw_none, cls.raw_lot, cls.raw_serial] + + # Workcenter + cls.workcenter = cls.env['mrp.workcenter'].create({ + 'name': 'Assembly Line', + }) + + # BoMs + cls.bom_none = cls.env['mrp.bom'].create({ + 'product_tmpl_id': cls.produced_none.product_tmpl_id.id, + 'product_uom_id': cls.produced_none.uom_id.id, + 'consumption': 'flexible', + 'sequence': 1 + }) + + cls.bom_none_lines = cls.create_bom_lines(cls.bom_none, cls.raws, [3, 2, 1]) + + cls.bom_lot = cls.env['mrp.bom'].create({ + 'product_tmpl_id': cls.produced_lot.product_tmpl_id.id, + 'product_uom_id': cls.produced_lot.uom_id.id, + 'consumption': 'flexible', + 'sequence': 2 + }) + + cls.bom_lot_lines = cls.create_bom_lines(cls.bom_lot, cls.raws, [3, 2, 1]) + + cls.bom_serial = cls.env['mrp.bom'].create({ + 'product_tmpl_id': cls.produced_serial.product_tmpl_id.id, + 'product_uom_id': cls.produced_serial.uom_id.id, + 'consumption': 'flexible', + 'sequence': 1 + }) + + cls.bom_serial_lines = cls.create_bom_lines(cls.bom_serial, cls.raws, [3, 2, 1]) + + # Manufacturing Orders + cls.mo_none_tmpl = { + 'product_id': cls.produced_none.id, + 'product_uom_id': cls.produced_none.uom_id.id, + 'product_qty': 1, + 'bom_id': cls.bom_none.id + } + + cls.mo_lot_tmpl = { + 'product_id': cls.produced_lot.id, + 'product_uom_id': cls.produced_lot.uom_id.id, + 'product_qty': 1, + 'bom_id': cls.bom_lot.id + } + + cls.mo_serial_tmpl = { + 'product_id': cls.produced_serial.id, + 'product_uom_id': cls.produced_serial.uom_id.id, + 'product_qty': 1, + 'bom_id': cls.bom_serial.id + } + + @classmethod + def create_quant(cls, product, qty, offset=0, name="L"): + i = 1 + if product.tracking == 'serial': + i, qty = qty, 1 + if name == "L": + name = "S" + + vals = [] + for x in range(1, i + 1): + qDict = { + 'location_id': cls.stock_id, + 'product_id': product.id, + 'inventory_quantity': qty, + } + + if product.tracking != 'none': + qDict['lot_id'] = cls.env['stock.lot'].create({ + 'name': name + str(offset + x), + 'product_id': product.id, + 'company_id': cls.env.company.id + }).id + vals.append(qDict) + + return cls.env['stock.quant'].create(vals) + + @classmethod + def create_bom_lines(cls, bom, products, quantities=None): + if quantities is None: + quantities = [1 for i in range(len(products))] + + vals = [] + for product, seq in zip(products, range(len(products))): + vals.append({ + 'product_id': product.id, + 'product_qty': quantities[seq], + 'product_uom_id': product.uom_id.id, + 'sequence': seq, + 'bom_id': bom.id, + }) + + return cls.env['mrp.bom.line'].create(vals) + + @classmethod + def create_mo(cls, template, count): + vals = [] + for _ in range(count): + vals.append(copy.deepcopy(template)) + mos = cls.env['mrp.production'].create(vals) + mos.move_raw_ids.mapped('manual_consumption') + return mos + + def executeConsumptionTriggers(self, mrp_productions): + """There's 3 different triggers to test : _onchange_producing(), action_generate_serial(), button_mark_done(). + + Depending on the tracking of the final product and the availability of the components, + only a part of these 3 triggers is available or intended to work. + + This function automatically call and process the appropriate triggers. + """ + tracking = mrp_productions[0].product_tracking + sameTracking = True + for mo in mrp_productions: + sameTracking = sameTracking and mo.product_tracking == tracking + self.assertTrue(sameTracking, "MOs passed to the executeConsumptionTriggers method shall have the same product_tracking") + + isSerial = tracking == 'serial' + isAvailable = all(move.state == 'assigned' for move in mrp_productions.move_raw_ids) + isComponentTracking = any(move.has_tracking != 'none' for move in mrp_productions.move_raw_ids) + + countOk = True + length = len(mrp_productions) + if isSerial: + if isAvailable: + countOk = length == self.SERIAL_AVAILABLE_TRIGGERS_COUNT + else: + countOk = length == self.SERIAL_TRIGGERS_COUNT + else: + if isAvailable: + countOk = length == self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + else: + countOk = length == self.DEFAULT_TRIGGERS_COUNT + self.assertTrue(countOk, "The number of MOs passed to the executeConsumptionTriggers method does not match the associated TRIGGERS_COUNT") + + mrp_productions[0].qty_producing = mrp_productions[0].product_qty + mrp_productions[0]._onchange_producing() + + i = 1 + if isSerial: + mrp_productions[i].action_generate_serial() + i += 1 + + if isAvailable: + error = False + try: + mrp_productions[i].button_mark_done() + except UserError: + error = True + + if isComponentTracking and not mrp_productions[i].picking_type_id.use_auto_consume_components_lots: + self.assertTrue(error, "Immediate Production shall raise an error when tracked product are not provided.") + else: + self.assertFalse(error, "Immediate Production shall not raise an error.") + + +@tagged('post_install', '-at_install') +class TestConsumeComponent(TestConsumeComponentCommon): + def test_option_disabled_and_qty_available(self): + """Option disabled, qty available + -> Not Tracked components are fully consumed + -> Tracked components are only consumed on button_mark_done trigger + """ + self.picking_type.use_auto_consume_components_lots = False + + mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) + mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT) + mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) + + mo_all = mo_none + mo_serial + mo_lot + mo_all.action_confirm() + + all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT + + quant = self.create_quant(self.raw_none, 3 * all_qty) + quant |= self.create_quant(self.raw_lot, 2 * all_qty) + quant |= self.create_quant(self.raw_serial, 1 * all_qty) + quant.action_apply_inventory() + + # Quantities are fully reserved (stock.move state is available) + mo_all.action_assign() + for mov in mo_all.move_raw_ids: + self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.") + + # Test for Serial Product + self.executeConsumptionTriggers(mo_serial) + self.executeConsumptionTriggers(mo_none) + self.executeConsumptionTriggers(mo_lot) + for mov in mo_all.move_raw_ids: + if mov.has_tracking == 'none' or mov.raw_material_production_id.state == 'done': + self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") + else: + self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to 0.") + + def test_option_enabled_and_qty_available(self): + """Option enabled, qty available + -> Not Tracked components are fully consumed + -> Tracked components are fully consumed + """ + + mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) + mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT) + mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) + + mo_all = mo_none + mo_serial + mo_lot + mo_all.action_confirm() + + all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT + + quant = self.create_quant(self.raw_none, 3*all_qty) + quant |= self.create_quant(self.raw_lot, 2*all_qty) + quant |= self.create_quant(self.raw_serial, 1*all_qty) + quant.action_apply_inventory() + + # Quantities are fully reserved (stock.move state is available) + mo_all.action_assign() + for mov in mo_all.move_raw_ids: + self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.") + + self.executeConsumptionTriggers(mo_serial) + self.executeConsumptionTriggers(mo_none) + self.executeConsumptionTriggers(mo_lot) + for mov in mo_all.move_raw_ids: + self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") + + def test_option_enabled_and_qty_not_available(self): + """Option enabled, qty not available + -> Not Tracked components are fully consumed + -> Tracked components are not consumed + """ + + mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_TRIGGERS_COUNT) + mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_TRIGGERS_COUNT) + mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_TRIGGERS_COUNT) + + mo_all = mo_none + mo_serial + mo_lot + mo_all.action_confirm() + + # Quantities are not reserved at all (stock.move state is confirmed) + mo_all.action_assign() + for mov in mo_all.move_raw_ids: + self.assertEqual(0, mov.reserved_availability, "Reserved quantity shall be equal to 0.") + + self.executeConsumptionTriggers(mo_serial) + self.executeConsumptionTriggers(mo_none) + self.executeConsumptionTriggers(mo_lot) + + for mov in mo_all.move_raw_ids: + if mov.has_tracking == 'none': + self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") + else: + self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") + + def test_option_enabled_and_qty_partially_available(self): + """Option enabled, qty partially available + -> Not Tracked components are fully consumed + -> Tracked components are partially consumed + """ + + # Update BoM serial component qty + self.bom_none_lines[2].product_qty = 2 + self.bom_serial_lines[2].product_qty = 2 + self.bom_lot_lines[2].product_qty = 2 + + raw_none_qty = 2 + raw_tracked_qty = 1 + + quant = self.create_quant(self.raw_none, raw_none_qty) + quant |= self.create_quant(self.raw_lot, raw_tracked_qty) + quant |= self.create_quant(self.raw_serial, raw_tracked_qty) + quant.action_apply_inventory() + + # We must create & process each MO at once as we must assign quants for each individually + def testUnit(mo_tmpl, serialTrigger=None): + mo = self.create_mo(mo_tmpl, 1) + mo.action_confirm() + + # are partially reserved (stock.move state is partially_available) + mo.action_assign() + for mov in mo.move_raw_ids: + if mov.has_tracking == "none": + self.assertEqual(raw_none_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_none_qty)+ ".") + else: + self.assertEqual(raw_tracked_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_tracked_qty)+ ".") + + if serialTrigger is None: + self.executeConsumptionTriggers(mo) + elif serialTrigger == 1: + mo.qty_producing = mo.product_qty + mo._onchange_producing() + elif serialTrigger == 2: + mo.action_generate_serial() + + for mov in mo.move_raw_ids: + if mov.has_tracking == "none": + self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") + else: + self.assertEqual(raw_tracked_qty, mov.quantity_done, "Done quantity shall be equal to " + str(raw_tracked_qty)+ ".") + mo.action_cancel() + + testUnit(self.mo_none_tmpl) + testUnit(self.mo_lot_tmpl) + testUnit(self.mo_serial_tmpl, 1) + testUnit(self.mo_serial_tmpl, 2) diff --git a/addons/mrp/tests/test_consume_tracked_component.py b/addons/mrp/tests/test_consume_tracked_component.py deleted file mode 100644 index cded3090949..00000000000 --- a/addons/mrp/tests/test_consume_tracked_component.py +++ /dev/null @@ -1,157 +0,0 @@ -from odoo.tests import tagged -from odoo.addons.mrp.tests.common_consume_tracked_component import TestConsumeTrackedComponentCommon - -@tagged('post_install', '-at_install') -class TestConsumeTrackedComponent(TestConsumeTrackedComponentCommon): - - def test_option_disabled_and_qty_available(self): - """ - Option disabled, qty available - -> Not Tracked components are fully consumed - -> Tracked components are only consumed on button_mark_done trigger - """ - - self.picking_type.use_auto_consume_components_lots = False - - mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) - mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT) - mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) - - mo_all = mo_none + mo_serial + mo_lot - mo_all.action_confirm() - - all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT - - quant = self.create_quant(self.raw_none, 3*all_qty) - quant |= self.create_quant(self.raw_lot, 2*all_qty) - quant |= self.create_quant(self.raw_serial, 1*all_qty) - quant.action_apply_inventory() - - #Quantities are fully reserved (stock.move state is available) - mo_all.action_assign() - for mov in mo_all.move_raw_ids: - self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.") - - #Test for Serial Product - self.executeConsumptionTriggers(mo_serial) - self.executeConsumptionTriggers(mo_none) - self.executeConsumptionTriggers(mo_lot) - for mov in mo_all.move_raw_ids: - if mov.has_tracking == 'none' or mov.raw_material_production_id.state == 'done': - self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") - else: - self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to 0.") - - def test_option_enabled_and_qty_available(self): - """ - Option enabled, qty available - -> Not Tracked components are fully consumed - -> Tracked components are fully consumed - """ - - mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) - mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT) - mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT) - - mo_all = mo_none + mo_serial + mo_lot - mo_all.action_confirm() - - all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT - - quant = self.create_quant(self.raw_none, 3*all_qty) - quant |= self.create_quant(self.raw_lot, 2*all_qty) - quant |= self.create_quant(self.raw_serial, 1*all_qty) - quant.action_apply_inventory() - - #Quantities are fully reserved (stock.move state is available) - mo_all.action_assign() - for mov in mo_all.move_raw_ids: - self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.") - - self.executeConsumptionTriggers(mo_serial) - self.executeConsumptionTriggers(mo_none) - self.executeConsumptionTriggers(mo_lot) - for mov in mo_all.move_raw_ids: - self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") - - def test_option_enabled_and_qty_not_available(self): - """ - Option enabled, qty not available - -> Not Tracked components are fully consumed - -> Tracked components are not consumed - """ - - mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_TRIGGERS_COUNT) - mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_TRIGGERS_COUNT) - mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_TRIGGERS_COUNT) - - mo_all = mo_none + mo_serial + mo_lot - mo_all.action_confirm() - - #Quantities are not reserved at all (stock.move state is confirmed) - mo_all.action_assign() - for mov in mo_all.move_raw_ids: - self.assertEqual(0, mov.reserved_availability, "Reserved quantity shall be equal to 0.") - - self.executeConsumptionTriggers(mo_serial) - self.executeConsumptionTriggers(mo_none) - self.executeConsumptionTriggers(mo_lot) - - for mov in mo_all.move_raw_ids: - if mov.has_tracking == 'none': - self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") - else: - self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") - - def test_option_enabled_and_qty_partially_available(self): - """ - Option enabled, qty partially available - -> Not Tracked components are fully consumed - -> Tracked components are partially consumed - """ - - #update BoM serial component qty - self.bom_none_lines[2].product_qty = 2 - self.bom_serial_lines[2].product_qty = 2 - self.bom_lot_lines[2].product_qty = 2 - - raw_none_qty = 2 - raw_tracked_qty = 1 - - quant = self.create_quant(self.raw_none, raw_none_qty) - quant |= self.create_quant(self.raw_lot, raw_tracked_qty) - quant |= self.create_quant(self.raw_serial, raw_tracked_qty) - quant.action_apply_inventory() - - #We must create & process each MO at once as we must assign quants for each individually - def testUnit(mo_tmpl, serialTrigger=None): - mo = self.create_mo(mo_tmpl, 1) - mo.action_confirm() - - #Quantities are partially reserved (stock.move state is partially_available) - mo.action_assign() - for mov in mo.move_raw_ids: - if mov.has_tracking == "none": - self.assertEqual(raw_none_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_none_qty)+ ".") - else: - self.assertEqual(raw_tracked_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_tracked_qty)+ ".") - - if serialTrigger is None: - self.executeConsumptionTriggers(mo) - elif serialTrigger == 1: - mo.qty_producing = mo.product_qty - mo._onchange_producing() - elif serialTrigger == 2: - mo.action_generate_serial() - - for mov in mo.move_raw_ids: - if mov.has_tracking == "none": - self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.") - else: - self.assertEqual(raw_tracked_qty, mov.quantity_done, "Done quantity shall be equal to " + str(raw_tracked_qty)+ ".") - mo.action_cancel() - - testUnit(self.mo_none_tmpl) - testUnit(self.mo_lot_tmpl) - testUnit(self.mo_serial_tmpl, 1) - testUnit(self.mo_serial_tmpl, 2)