From 04ccd15b70265ec5e856fa9378e6c8d0915ba9e9 Mon Sep 17 00:00:00 2001
From: arbaes <arb@odoo.com>
Date: Thu, 22 Nov 2018 14:39:39 +0000
Subject: [PATCH] [IMP] mrp: quantity_done on kits in immediate transfers

With this commit, when a kit is processed trough an immediate transfer,
the quantity done set on the kit is now propagated to its components.

TaskID: 1896772

closes odoo/odoo#28998
---
 addons/mrp/models/stock_move.py            |  19 ++-
 addons/mrp/tests/test_stock.py             | 173 +++++++++++++++++++++
 addons/purchase_mrp/models/purchase_mrp.py |   4 +-
 3 files changed, 188 insertions(+), 8 deletions(-)

diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 83444b708a7e..096074b81573 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -163,10 +163,16 @@ class StockMove(models.Model):
             return self
         phantom_moves = self.env['stock.move']
         processed_moves = self.env['stock.move']
-        factor = self.product_uom._compute_quantity(self.product_uom_qty, bom.product_uom_id) / bom.product_qty
+        if self.picking_id.immediate_transfer:
+            factor = self.product_uom._compute_quantity(self.quantity_done, bom.product_uom_id) / bom.product_qty
+        else:
+            factor = self.product_uom._compute_quantity(self.product_uom_qty, bom.product_uom_id) / bom.product_qty
         boms, lines = bom.sudo().explode(self.product_id, factor, picking_type=bom.picking_type_id)
         for bom_line, line_data in lines:
-            phantom_moves += self._generate_move_phantom(bom_line, line_data['qty'])
+            if self.picking_id.immediate_transfer:
+                phantom_moves += self._generate_move_phantom(bom_line, 0, line_data['qty'])
+            else:
+                phantom_moves += self._generate_move_phantom(bom_line, line_data['qty'], 0)
 
         for new_move in phantom_moves:
             processed_moves |= new_move.action_explode()
@@ -182,19 +188,20 @@ class StockMove(models.Model):
         self.sudo().unlink()
         return processed_moves
 
-    def _prepare_phantom_move_values(self, bom_line, quantity):
+    def _prepare_phantom_move_values(self, bom_line, product_qty, quantity_done):
         return {
             'picking_id': self.picking_id.id if self.picking_id else False,
             'product_id': bom_line.product_id.id,
             'product_uom': bom_line.product_uom_id.id,
-            'product_uom_qty': quantity,
+            'product_uom_qty': product_qty,
+            'quantity_done': quantity_done,
             'state': 'draft',  # will be confirmed below
             'name': self.name,
         }
 
-    def _generate_move_phantom(self, bom_line, quantity):
+    def _generate_move_phantom(self, bom_line, product_qty, quantity_done):
         if bom_line.product_id.type in ['product', 'consu']:
-            return self.copy(default=self._prepare_phantom_move_values(bom_line, quantity))
+            return self.copy(default=self._prepare_phantom_move_values(bom_line, product_qty, quantity_done))
         return self.env['stock.move']
 
     def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False):
diff --git a/addons/mrp/tests/test_stock.py b/addons/mrp/tests/test_stock.py
index 3b0d5edcac43..da3db148b687 100644
--- a/addons/mrp/tests/test_stock.py
+++ b/addons/mrp/tests/test_stock.py
@@ -102,3 +102,176 @@ class TestWarehouse(common.TestMrpCommon):
 
 #        scrap_move = production_3.move_raw_ids.filtered(lambda x: x.product_id == self.product_2 and x.scrapped)
 #        self.assertTrue(scrap_move, "There are no any scrap move created for production order.")
+
+
+class TestKitPicking(common.TestMrpCommon):
+    def setUp(self):
+        super(TestKitPicking, self).setUp()
+
+        def create_product(name):
+            p = Form(self.env['product.product'])
+            p.name = name
+            p.type = 'product'
+            return p.save()
+
+        # 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 components
+        component_a = create_product('Comp A')
+        component_b = create_product('Comp B')
+        component_c = create_product('Comp C')
+        component_d = create_product('Comp D')
+        component_e = create_product('Comp E')
+        component_f = create_product('Comp F')
+        component_g = create_product('Comp G')
+        # Creating all kits
+        kit_1 = create_product('Kit 1')
+        kit_2 = create_product('Kit 2')
+        kit_3 = create_product('kit 3')
+        self.kit_parent = create_product('Kit Parent')
+        # Linking the kits and the components via some 'phantom' BoMs
+        bom_kit_1 = self.env['mrp.bom'].create({
+            'product_tmpl_id': kit_1.product_tmpl_id.id,
+            'product_qty': 1.0,
+            'type': 'phantom'})
+        BomLine = self.env['mrp.bom.line']
+        BomLine.create({
+            'product_id': component_a.id,
+            'product_qty': 2.0,
+            'bom_id': bom_kit_1.id})
+        BomLine.create({
+            'product_id': component_b.id,
+            'product_qty': 1.0,
+            'bom_id': bom_kit_1.id})
+        BomLine.create({
+            'product_id': component_c.id,
+            'product_qty': 3.0,
+            'bom_id': bom_kit_1.id})
+        bom_kit_2 = self.env['mrp.bom'].create({
+            'product_tmpl_id': kit_2.product_tmpl_id.id,
+            'product_qty': 1.0,
+            'type': 'phantom'})
+        BomLine.create({
+            'product_id': component_d.id,
+            'product_qty': 1.0,
+            'bom_id': bom_kit_2.id})
+        BomLine.create({
+            'product_id': 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': component_e.id,
+            'product_qty': 1.0,
+            'bom_id': bom_kit_parent.id})
+        BomLine.create({
+            'product_id': kit_2.id,
+            'product_qty': 2.0,
+            'bom_id': bom_kit_parent.id})
+        bom_kit_3 = self.env['mrp.bom'].create({
+            'product_tmpl_id': kit_3.product_tmpl_id.id,
+            'product_qty': 1.0,
+            'type': 'phantom'})
+        BomLine.create({
+            'product_id': component_f.id,
+            'product_qty': 1.0,
+            'bom_id': bom_kit_3.id})
+        BomLine.create({
+            'product_id': component_g.id,
+            'product_qty': 2.0,
+            'bom_id': bom_kit_3.id})
+        BomLine.create({
+            'product_id': kit_3.id,
+            'product_qty': 1.0,
+            'bom_id': bom_kit_parent.id})
+
+        # We create an 'immediate transfer' receipt for x3 kit_parent
+        self.test_partner = self.env['res.partner'].create({
+            'name': 'Notthat Guyagain',
+            'supplier': True,
+            'customer': True,
+        })
+        self.test_supplier = self.env['stock.location'].create({
+            'name': 'supplier',
+            'usage': 'supplier',
+            'location_id': self.env.ref('stock.stock_location_stock').id,
+        })
+
+        self.expected_quantities = {
+            component_a: 24,
+            component_b: 12,
+            component_c: 36,
+            component_d: 6,
+            component_e: 3,
+            component_f: 3,
+            component_g: 6
+        }
+
+    def test_kit_immediate_transfer(self):
+        """ Make sure a kit is split in the corrects quantity_done by components in case of an
+        immediate transfer.
+        """
+        picking = self.env['stock.picking'].create({
+            'location_id': self.test_supplier.id,
+            'location_dest_id': self.warehouse_1.wh_input_stock_loc_id.id,
+            'partner_id': self.test_partner.id,
+            'picking_type_id': self.env.ref('stock.picking_type_in').id,
+            'immediate_transfer': True
+        })
+        move_receipt_1 = self.env['stock.move'].create({
+            'name': self.kit_parent.name,
+            'product_id': self.kit_parent.id,
+            'quantity_done': 3,
+            'product_uom': self.kit_parent.uom_id.id,
+            'picking_id': picking.id,
+            'picking_type_id': self.env.ref('stock.picking_type_in').id,
+            'location_id':  self.test_supplier.id,
+            'location_dest_id': self.warehouse_1.wh_input_stock_loc_id.id,
+        })
+        picking.button_validate()
+
+        # We check that the picking has the correct quantities after its move were splitted.
+        self.assertEquals(len(picking.move_lines), 7)
+        for move_line in picking.move_lines:
+            self.assertEquals(move_line.quantity_done, self.expected_quantities[move_line.product_id])
+
+    def test_kit_planned_transfer(self):
+        """ Make sure a kit is split in the corrects product_qty by components in case of a
+        planned transfer.
+        """
+        picking = self.env['stock.picking'].create({
+            'location_id': self.test_supplier.id,
+            'location_dest_id': self.warehouse_1.wh_input_stock_loc_id.id,
+            'partner_id': self.test_partner.id,
+            'picking_type_id': self.env.ref('stock.picking_type_in').id,
+            'immediate_transfer': False,
+        })
+        move_receipt_1 = self.env['stock.move'].create({
+            'name': self.kit_parent.name,
+            'product_id': self.kit_parent.id,
+            'product_uom_qty': 3,
+            'product_uom': self.kit_parent.uom_id.id,
+            'picking_id': picking.id,
+            'picking_type_id': self.env.ref('stock.picking_type_in').id,
+            'location_id':  self.test_supplier.id,
+            'location_dest_id': self.warehouse_1.wh_input_stock_loc_id.id,
+        })
+        picking.action_confirm()
+
+        # We check that the picking has the correct quantities after its move were splitted.
+        self.assertEquals(len(picking.move_lines), 7)
+        for move_line in picking.move_lines:
+            self.assertEquals(move_line.product_qty, self.expected_quantities[move_line.product_id])
diff --git a/addons/purchase_mrp/models/purchase_mrp.py b/addons/purchase_mrp/models/purchase_mrp.py
index 176c86cb358d..20d102440150 100644
--- a/addons/purchase_mrp/models/purchase_mrp.py
+++ b/addons/purchase_mrp/models/purchase_mrp.py
@@ -42,8 +42,8 @@ class PurchaseOrderLine(models.Model):
 class StockMove(models.Model):
     _inherit = 'stock.move'
 
-    def _prepare_phantom_move_values(self, bom_line, quantity):
-        vals = super(StockMove, self)._prepare_phantom_move_values(bom_line, quantity)
+    def _prepare_phantom_move_values(self, bom_line, product_qty, quantity_done):
+        vals = super(StockMove, self)._prepare_phantom_move_values(bom_line, product_qty, quantity_done)
         if self.purchase_line_id:
             vals['purchase_line_id'] = self.purchase_line_id.id
         return vals
-- 
GitLab