summaryrefslogtreecommitdiff
path: root/addons/purchase_mrp/tests
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/purchase_mrp/tests
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/purchase_mrp/tests')
-rw-r--r--addons/purchase_mrp/tests/__init__.py4
-rw-r--r--addons/purchase_mrp/tests/test_purchase_mrp_flow.py411
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)