diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/purchase_mrp/tests | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/purchase_mrp/tests')
| -rw-r--r-- | addons/purchase_mrp/tests/__init__.py | 4 | ||||
| -rw-r--r-- | addons/purchase_mrp/tests/test_purchase_mrp_flow.py | 411 |
2 files changed, 415 insertions, 0 deletions
diff --git a/addons/purchase_mrp/tests/__init__.py b/addons/purchase_mrp/tests/__init__.py new file mode 100644 index 00000000..45619da7 --- /dev/null +++ b/addons/purchase_mrp/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_purchase_mrp_flow diff --git a/addons/purchase_mrp/tests/test_purchase_mrp_flow.py b/addons/purchase_mrp/tests/test_purchase_mrp_flow.py new file mode 100644 index 00000000..b5c799d7 --- /dev/null +++ b/addons/purchase_mrp/tests/test_purchase_mrp_flow.py @@ -0,0 +1,411 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import Form, TransactionCase +from odoo.tests import Form + + +class TestSaleMrpFlow(TransactionCase): + + def setUp(self): + super(TestSaleMrpFlow, self).setUp() + # Useful models + self.UoM = self.env['uom.uom'] + self.categ_unit = self.env.ref('uom.product_uom_categ_unit') + self.categ_kgm = self.env.ref('uom.product_uom_categ_kgm') + self.stock_location = self.env.ref('stock.stock_location_stock') + self.warehouse = self.env.ref('stock.warehouse0') + + self.uom_kg = self.env['uom.uom'].search([('category_id', '=', self.categ_kgm.id), ('uom_type', '=', 'reference')], + limit=1) + self.uom_kg.write({ + 'name': 'Test-KG', + 'rounding': 0.000001}) + self.uom_gm = self.UoM.create({ + 'name': 'Test-G', + 'category_id': self.categ_kgm.id, + 'uom_type': 'smaller', + 'factor': 1000.0, + 'rounding': 0.001}) + self.uom_unit = self.env['uom.uom'].search( + [('category_id', '=', self.categ_unit.id), ('uom_type', '=', 'reference')], limit=1) + self.uom_unit.write({ + 'name': 'Test-Unit', + 'rounding': 0.01}) + self.uom_dozen = self.UoM.create({ + 'name': 'Test-DozenA', + 'category_id': self.categ_unit.id, + 'factor_inv': 12, + 'uom_type': 'bigger', + 'rounding': 0.001}) + + # Creating all components + self.component_a = self._create_product('Comp A', self.uom_unit) + self.component_b = self._create_product('Comp B', self.uom_unit) + self.component_c = self._create_product('Comp C', self.uom_unit) + self.component_d = self._create_product('Comp D', self.uom_unit) + self.component_e = self._create_product('Comp E', self.uom_unit) + self.component_f = self._create_product('Comp F', self.uom_unit) + self.component_g = self._create_product('Comp G', self.uom_unit) + + # Create a kit 'kit_1' : + # ----------------------- + # + # kit_1 --|- component_a x2 + # |- component_b x1 + # |- component_c x3 + + self.kit_1 = self._create_product('Kit 1', self.uom_unit) + + self.bom_kit_1 = self.env['mrp.bom'].create({ + 'product_tmpl_id': self.kit_1.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom'}) + + BomLine = self.env['mrp.bom.line'] + BomLine.create({ + 'product_id': self.component_a.id, + 'product_qty': 2.0, + 'bom_id': self.bom_kit_1.id}) + BomLine.create({ + 'product_id': self.component_b.id, + 'product_qty': 1.0, + 'bom_id': self.bom_kit_1.id}) + BomLine.create({ + 'product_id': self.component_c.id, + 'product_qty': 3.0, + 'bom_id': self.bom_kit_1.id}) + + # Create a kit 'kit_parent' : + # --------------------------- + # + # kit_parent --|- kit_2 x2 --|- component_d x1 + # | |- kit_1 x2 -------|- component_a x2 + # | |- component_b x1 + # | |- component_c x3 + # | + # |- kit_3 x1 --|- component_f x1 + # | |- component_g x2 + # | + # |- component_e x1 + + # Creating all kits + self.kit_2 = self._create_product('Kit 2', self.uom_unit) + self.kit_3 = self._create_product('kit 3', self.uom_unit) + self.kit_parent = self._create_product('Kit Parent', self.uom_unit) + + # Linking the kits and the components via some 'phantom' BoMs + bom_kit_2 = self.env['mrp.bom'].create({ + 'product_tmpl_id': self.kit_2.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom'}) + + BomLine.create({ + 'product_id': self.component_d.id, + 'product_qty': 1.0, + 'bom_id': bom_kit_2.id}) + BomLine.create({ + 'product_id': self.kit_1.id, + 'product_qty': 2.0, + 'bom_id': bom_kit_2.id}) + + bom_kit_parent = self.env['mrp.bom'].create({ + 'product_tmpl_id': self.kit_parent.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom'}) + + BomLine.create({ + 'product_id': self.component_e.id, + 'product_qty': 1.0, + 'bom_id': bom_kit_parent.id}) + BomLine.create({ + 'product_id': self.kit_2.id, + 'product_qty': 2.0, + 'bom_id': bom_kit_parent.id}) + + bom_kit_3 = self.env['mrp.bom'].create({ + 'product_tmpl_id': self.kit_3.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom'}) + + BomLine.create({ + 'product_id': self.component_f.id, + 'product_qty': 1.0, + 'bom_id': bom_kit_3.id}) + BomLine.create({ + 'product_id': self.component_g.id, + 'product_qty': 2.0, + 'bom_id': bom_kit_3.id}) + + BomLine.create({ + 'product_id': self.kit_3.id, + 'product_qty': 2.0, + 'bom_id': bom_kit_parent.id}) + + def _create_product(self, name, uom_id, routes=()): + p = Form(self.env['product.product']) + p.name = name + p.type = 'product' + p.uom_id = uom_id + p.uom_po_id = uom_id + p.route_ids.clear() + for r in routes: + p.route_ids.add(r) + return p.save() + + # Helper to process quantities based on a dict following this structure : + # + # qty_to_process = { + # product_id: qty + # } + + def _process_quantities(self, moves, quantities_to_process): + """ Helper to process quantities based on a dict following this structure : + qty_to_process = { + product_id: qty + } + """ + moves_to_process = moves.filtered(lambda m: m.product_id in quantities_to_process.keys()) + for move in moves_to_process: + move.write({'quantity_done': quantities_to_process[move.product_id]}) + + def _assert_quantities(self, moves, quantities_to_process): + """ Helper to check expected quantities based on a dict following this structure : + qty_to_process = { + product_id: qty + ... + } + """ + moves_to_process = moves.filtered(lambda m: m.product_id in quantities_to_process.keys()) + for move in moves_to_process: + self.assertEqual(move.product_uom_qty, quantities_to_process[move.product_id]) + + def _create_move_quantities(self, qty_to_process, components, warehouse): + """ Helper to creates moves in order to update the quantities of components + on a specific warehouse. This ensure that all compute fields are triggered. + The structure of qty_to_process should be the following : + + qty_to_process = { + component: (qty, uom), + ... + } + """ + for comp in components: + f = Form(self.env['stock.move']) + f.name = 'Test Receipt Components' + f.location_id = self.env.ref('stock.stock_location_suppliers') + f.location_dest_id = warehouse.lot_stock_id + f.product_id = comp + f.product_uom = qty_to_process[comp][1] + f.product_uom_qty = qty_to_process[comp][0] + move = f.save() + move._action_confirm() + move._action_assign() + move_line = move.move_line_ids[0] + move_line.qty_done = qty_to_process[comp][0] + move._action_done() + + def test_01_sale_mrp_kit_qty_delivered(self): + """ Test that the quantities delivered are correct when + a kit with subkits is ordered with multiple backorders and returns + """ + + # 'kit_parent' structure: + # --------------------------- + # + # kit_parent --|- kit_2 x2 --|- component_d x1 + # | |- kit_1 x2 -------|- component_a x2 + # | |- component_b x1 + # | |- component_c x3 + # | + # |- kit_3 x1 --|- component_f x1 + # | |- component_g x2 + # | + # |- component_e x1 + + # Creation of a sale order for x7 kit_parent + partner = self.env['res.partner'].create({'name': 'My Test Partner'}) + f = Form(self.env['purchase.order']) + f.partner_id = partner + with f.order_line.new() as line: + line.product_id = self.kit_parent + line.product_qty = 7.0 + line.price_unit = 10 + + po = f.save() + po.button_confirm() + + # Check picking creation, its move lines should concern + # only components. Also checks that the quantities are corresponding + # to the PO + self.assertEqual(len(po.picking_ids), 1) + order_line = po.order_line[0] + picking_original = po.picking_ids[0] + move_lines = picking_original.move_lines + products = move_lines.mapped('product_id') + kits = [self.kit_parent, self.kit_3, self.kit_2, self.kit_1] + components = [self.component_a, self.component_b, self.component_c, self.component_d, self.component_e, + self.component_f, self.component_g] + expected_quantities = { + self.component_a: 56.0, + self.component_b: 28.0, + self.component_c: 84.0, + self.component_d: 14.0, + self.component_e: 7.0, + self.component_f: 14.0, + self.component_g: 28.0 + } + + self.assertEqual(len(move_lines), 7) + self.assertTrue(not any(kit in products for kit in kits)) + self.assertTrue(all(component in products for component in components)) + self._assert_quantities(move_lines, expected_quantities) + + # Process only 7 units of each component + qty_to_process = 7 + move_lines.write({'quantity_done': qty_to_process}) + + # Create a backorder for the missing componenents + pick = po.picking_ids[0] + res = pick.button_validate() + Form(self.env[res['res_model']].with_context(res['context'])).save().process() + + # Check that a backorded is created + self.assertEqual(len(po.picking_ids), 2) + backorder_1 = po.picking_ids - picking_original + self.assertEqual(backorder_1.backorder_id.id, picking_original.id) + + # Even if some components are received completely, + # no KitParent should be received + self.assertEqual(order_line.qty_received, 0) + + # Process just enough components to make 1 kit_parent + qty_to_process = { + self.component_a: 1, + self.component_c: 5, + } + self._process_quantities(backorder_1.move_lines, qty_to_process) + + # Create a backorder for the missing componenents + res = backorder_1.button_validate() + Form(self.env[res['res_model']].with_context(res['context'])).save().process() + + # Only 1 kit_parent should be received at this point + self.assertEqual(order_line.qty_received, 1) + + # Check that the second backorder is created + self.assertEqual(len(po.picking_ids), 3) + backorder_2 = po.picking_ids - picking_original - backorder_1 + self.assertEqual(backorder_2.backorder_id.id, backorder_1.id) + + # Set the components quantities that backorder_2 should have + expected_quantities = { + self.component_a: 48, + self.component_b: 21, + self.component_c: 72, + self.component_d: 7, + self.component_f: 7, + self.component_g: 21 + } + + # Check that the computed quantities are matching the theorical ones. + # Since component_e was totally processed, this componenent shouldn't be + # present in backorder_2 + self.assertEqual(len(backorder_2.move_lines), 6) + move_comp_e = backorder_2.move_lines.filtered(lambda m: m.product_id.id == self.component_e.id) + self.assertFalse(move_comp_e) + self._assert_quantities(backorder_2.move_lines, expected_quantities) + + # Process enough components to make x3 kit_parents + qty_to_process = { + self.component_a: 16, + self.component_b: 5, + self.component_c: 24, + self.component_g: 5 + } + self._process_quantities(backorder_2.move_lines, qty_to_process) + + # Create a backorder for the missing componenents + res = backorder_2.button_validate() + Form(self.env[res['res_model']].with_context(res['context'])).save().process() + + # Check that x3 kit_parents are indeed received + self.assertEqual(order_line.qty_received, 3) + + # Check that the third backorder is created + self.assertEqual(len(po.picking_ids), 4) + backorder_3 = po.picking_ids - (picking_original + backorder_1 + backorder_2) + self.assertEqual(backorder_3.backorder_id.id, backorder_2.id) + + # Check the components quantities that backorder_3 should have + expected_quantities = { + self.component_a: 32, + self.component_b: 16, + self.component_c: 48, + self.component_d: 7, + self.component_f: 7, + self.component_g: 16 + } + self._assert_quantities(backorder_3.move_lines, expected_quantities) + + # Process all missing components + self._process_quantities(backorder_3.move_lines, expected_quantities) + + # Validating the last backorder now it's complete. + # All kits should be received + backorder_3.button_validate() + self.assertEqual(order_line.qty_received, 7.0) + + # Return all components processed by backorder_3 + stock_return_picking_form = Form(self.env['stock.return.picking'] + .with_context(active_ids=backorder_3.ids, active_id=backorder_3.ids[0], + active_model='stock.picking')) + return_wiz = stock_return_picking_form.save() + for return_move in return_wiz.product_return_moves: + return_move.write({ + 'quantity': expected_quantities[return_move.product_id], + 'to_refund': True + }) + res = return_wiz.create_returns() + return_pick = self.env['stock.picking'].browse(res['res_id']) + + # Process all components and validate the picking + wiz_act = return_pick.button_validate() + wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save() + wiz.process() + + # Now quantity received should be 3 again + self.assertEqual(order_line.qty_received, 3) + + stock_return_picking_form = Form(self.env['stock.return.picking'] + .with_context(active_ids=return_pick.ids, active_id=return_pick.ids[0], + active_model='stock.picking')) + return_wiz = stock_return_picking_form.save() + for move in return_wiz.product_return_moves: + move.quantity = expected_quantities[move.product_id] + res = return_wiz.create_returns() + return_of_return_pick = self.env['stock.picking'].browse(res['res_id']) + + # Process all components except one of each + for move in return_of_return_pick.move_lines: + move.write({ + 'quantity_done': expected_quantities[move.product_id] - 1, + 'to_refund': True + }) + + wiz_act = return_of_return_pick.button_validate() + wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save() + wiz.process() + + # As one of each component is missing, only 6 kit_parents should be received + self.assertEqual(order_line.qty_received, 6) + + # Check that the 4th backorder is created. + self.assertEqual(len(po.picking_ids), 7) + backorder_4 = po.picking_ids - ( + picking_original + backorder_1 + backorder_2 + backorder_3 + return_of_return_pick + return_pick) + self.assertEqual(backorder_4.backorder_id.id, return_of_return_pick.id) + + # Check the components quantities that backorder_4 should have + for move in backorder_4.move_lines: + self.assertEqual(move.product_qty, 1) |
