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