From 380b84bd091cc9ce3b04ce7c178eac2a33333b0e Mon Sep 17 00:00:00 2001 From: Arnaud Baes Date: Tue, 4 Dec 2018 12:48:40 +0000 Subject: [PATCH] [IMP] sale_mrp: Kit quantities delivered Before this commit, the quantity_delivered of a kit was set to 0 unless all its components were delivered. With this commit, the quantity_delivered is the minimal quantity it's possible to make with the components currently delivered. TaskID: 1863856 --- addons/mrp/models/stock_move.py | 1 + addons/mrp/models/stock_rule.py | 6 ++++ addons/sale_mrp/models/sale_mrp.py | 54 +++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 610c1934b29..c7e884700ce 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -197,6 +197,7 @@ class StockMove(models.Model): 'quantity_done': quantity_done, 'state': 'draft', # will be confirmed below 'name': self.name, + 'bom_line_id': bom_line.id, } def _generate_move_phantom(self, bom_line, product_qty, quantity_done): diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py index e06be0abd98..cdeca1b4db5 100644 --- a/addons/mrp/models/stock_rule.py +++ b/addons/mrp/models/stock_rule.py @@ -54,6 +54,11 @@ class StockRule(models.Model): subtype_id=self.env.ref('mail.mt_note').id) return True + def _get_custom_move_fields(self): + fields = super(StockRule, self)._get_custom_move_fields() + fields += ['bom_line_id'] + return fields + @api.multi def _get_matching_bom(self, product_id, values): if values.get('bom_id', False): @@ -108,6 +113,7 @@ class ProcurementGroup(models.Model): bom_line_uom = bom_line.product_uom_id quant_uom = bom_line.product_id.uom_id component_qty, procurement_uom = bom_line_uom._adjust_uom_quantities(bom_line_data['qty'], quant_uom) + values['bom_line_id'] = bom_line.id super(ProcurementGroup, self).run(bom_line.product_id, component_qty, procurement_uom, location_id, name, origin, values) return True else: diff --git a/addons/sale_mrp/models/sale_mrp.py b/addons/sale_mrp/models/sale_mrp.py index f1bdfc3f9de..fb471fd2823 100644 --- a/addons/sale_mrp/models/sale_mrp.py +++ b/addons/sale_mrp/models/sale_mrp.py @@ -10,20 +10,50 @@ class SaleOrderLine(models.Model): @api.multi def _compute_qty_delivered(self): + """ Computes the quantity delivered when a kit is sold. + To achieve this, this method fetch the quantities delivered of + each component of the kit and the quantity needed of each components to produce 1 kit. + Based on this, a ratio is computed for each component, + and the lowest one is kept to define the kit's quantity delivered. + """ super(SaleOrderLine, self)._compute_qty_delivered() - - for line in self: - if line.qty_delivered_method == 'stock_move': - # In the case of a kit, we need to check if all components are shipped. Since the BOM might - # have changed, we don't compute the quantities but verify the move state. - bom = self.env['mrp.bom']._bom_find(product=line.product_id) - if bom and bom.type == 'phantom': - moves = line.move_ids.filtered(lambda m: m.picking_id and m.picking_id.state != 'cancel') - bom_delivered = all([move.state == 'done' for move in moves]) - if bom_delivered: - line.qty_delivered = line.product_uom_qty + for order_line in self: + if order_line.qty_delivered_method == 'stock_move': + boms = order_line.move_ids.mapped('bom_line_id.bom_id') + # We fetch the BoMs of type kits linked to the order_line, + # the we keep only the one related to the finished produst. + # This bom shoud be the only one since bom_line_id was written on the moves + relevant_bom = boms.filtered(lambda b: b.type == 'phantom' and + (b.product_id == order_line.product_id or + (b.product_tmpl_id == order_line.product_id.product_tmpl_id and not b.product_id))) + if relevant_bom: + moves = order_line.move_ids.filtered(lambda m: m.state == 'done' and not m.scrapped) + qty_ratios = [] + order_uom_qty = order_line.product_uom._compute_quantity(order_line.product_uom_qty, relevant_bom.product_uom_id) + boms, bom_sub_lines = relevant_bom.explode(order_line.product_id, order_uom_qty) + for bom_line, bom_line_data in bom_sub_lines: + bom_line_moves = moves.filtered(lambda m: m.bom_line_id == bom_line) + if bom_line_moves: + # We compute the quantities needed of each components to make one kit. + # Then, we collect every relevant moves related to a specific component + # to know how many are considered delivered. + uom_qty_per_kit = bom_line_data['qty'] / bom_line_data['original_qty'] + qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id) + delivery_moves = bom_line_moves.filtered(lambda m: m.location_dest_id.usage == 'customer' and not m.origin_returned_move_id or not (m.origin_returned_move_id and m.to_refund)) + return_moves = bom_line_moves.filtered(lambda m: m.location_dest_id.usage != 'customer' and m.to_refund) + qty_delivered = sum(delivery_moves.mapped('product_qty')) - sum(return_moves.mapped('product_qty')) + # We compute a ratio to know how many kits we can produce with this quantity of that specific component + qty_ratios.append(qty_delivered / qty_per_kit) + else: + qty_ratios.append(0.0) + break + if qty_ratios: + # Now that we have every ratio by components, we keep the lowest one to know how many kits we can produce + # with the quantities delivered of each component. We use the euclidean division here because a 'partial kit' + # doesn't make sense. + order_line.qty_delivered = min(qty_ratios) // 1 else: - line.qty_delivered = 0.0 + order_line.qty_delivered = 0.0 @api.multi def _get_bom_component_qty(self, bom):