From d4f82ff1facfe95d43c6b36a36953ccd244ba511 Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Thu, 29 Dec 2022 13:00:46 +0000 Subject: [PATCH] [FIX] purchase_stock: generate vendor delay report The vendor delay report only works with basic use cases. Here are the issues: - For each SM, we sum up the done qty of all SMLs without considering the UoMs - When grouping several lines (for instance, by partner and product), we sum up the quantities of each line but their UoM can be different - When grouping several lines, we sum the `qty_total` which are the quantity of the related POL. This is an issue: suppose a POL with a quantity Q and suppose there are two SM (backorder), if we group by product, we will have a total quantity of 2Q, which is incorrect (and so will be the delivery rate) To reproduce the issues: See the tests added by this commit. Note: the last test (`test_vendor_delay_report_without_backorder`) was not failing before this commit. It is added to simply improve the tests OPW-3065065 closes odoo/odoo#109471 X-original-commit: c2e351534b9627b6844effba9943b09d4b0b93f3 Signed-off-by: Arnold Moyaux (arm) Signed-off-by: Adrien Widart --- .../report/vendor_delay_report.py | 13 +- .../tests/test_purchase_stock_report.py | 159 ++++++++++++++++++ 2 files changed, 167 insertions(+), 5 deletions(-) diff --git a/addons/purchase_stock/report/vendor_delay_report.py b/addons/purchase_stock/report/vendor_delay_report.py index ef55b2ca434..dde37851510 100644 --- a/addons/purchase_stock/report/vendor_delay_report.py +++ b/addons/purchase_stock/report/vendor_delay_report.py @@ -29,14 +29,12 @@ SELECT m.id AS id, m.product_id AS product_id, Min(pc.id) AS category_id, Min(po.partner_id) AS partner_id, - Sum(pol.product_uom_qty) AS qty_total, + Min(m.product_qty) AS qty_total, Sum(CASE - WHEN (pol.date_planned::date >= m.date::date) THEN ml.qty_done + WHEN (m.state = 'done' and pol.date_planned::date >= m.date::date) THEN (ml.qty_done / ml_uom.factor * pt_uom.factor) ELSE 0 END) AS qty_on_time FROM stock_move m - JOIN stock_move_line ml - ON m.id = ml.move_id JOIN purchase_order_line pol ON pol.id = m.purchase_line_id JOIN purchase_order po @@ -45,9 +43,14 @@ FROM stock_move m ON p.id = m.product_id JOIN product_template pt ON pt.id = p.product_tmpl_id + JOIN uom_uom pt_uom + ON pt_uom.id = pt.uom_id JOIN product_category pc ON pc.id = pt.categ_id -WHERE m.state = 'done' + LEFT JOIN stock_move_line ml + ON ml.move_id = m.id + LEFT JOIN uom_uom ml_uom + ON ml_uom.id = ml.product_uom_id GROUP BY m.id )""") diff --git a/addons/purchase_stock/tests/test_purchase_stock_report.py b/addons/purchase_stock/tests/test_purchase_stock_report.py index bc2af7f83f4..0e71f547a9e 100644 --- a/addons/purchase_stock/tests/test_purchase_stock_report.py +++ b/addons/purchase_stock/tests/test_purchase_stock_report.py @@ -202,3 +202,162 @@ class TestPurchaseStockReports(TestReportsCommon): docs = self.get_report_forecast(product_template_ids=self.product_template.ids)[1] self.assertEqual(docs['draft_purchase_qty'], 150) + + def test_vendor_delay_report_with_uom(self): + """ + PO 12 units x P + Receive 1 dozen x P + -> 100% received + """ + uom_12 = self.env.ref('uom.product_uom_dozen') + + po_form = Form(self.env['purchase.order']) + po_form.partner_id = self.partner + with po_form.order_line.new() as line: + line.product_id = self.product + line.product_qty = 12 + po = po_form.save() + po.button_confirm() + + receipt = po.picking_ids + receipt_move = receipt.move_ids + receipt_move.move_line_ids.unlink() + receipt_move.move_line_ids = [(0, 0, { + 'location_id': receipt_move.location_id.id, + 'location_dest_id': receipt_move.location_dest_id.id, + 'product_id': self.product.id, + 'product_uom_id': uom_12.id, + 'qty_done': 1, + 'picking_id': receipt.id, + })] + receipt.button_validate() + + data = self.env['vendor.delay.report'].read_group( + [('partner_id', '=', self.partner.id)], + ['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'], + ['product_id'], + )[0] + self.assertEqual(data['qty_on_time'], 12) + self.assertEqual(data['qty_total'], 12) + self.assertEqual(data['on_time_rate'], 100) + + def test_vendor_delay_report_with_multi_location(self): + """ + PO 10 units x P + Receive + - 6 x P in Child Location 01 + - 4 x P in Child Location 02 + -> 100% received + """ + child_loc_01, child_loc_02 = self.stock_location.child_ids + + po_form = Form(self.env['purchase.order']) + po_form.partner_id = self.partner + with po_form.order_line.new() as line: + line.product_id = self.product + line.product_qty = 10 + po = po_form.save() + po.button_confirm() + + receipt = po.picking_ids + receipt_move = receipt.move_ids + receipt_move.move_line_ids.unlink() + receipt_move.move_line_ids = [(0, 0, { + 'location_id': receipt_move.location_id.id, + 'location_dest_id': child_loc_01.id, + 'product_id': self.product.id, + 'product_uom_id': self.product.uom_id.id, + 'qty_done': 6, + 'picking_id': receipt.id, + }), (0, 0, { + 'location_id': receipt_move.location_id.id, + 'location_dest_id': child_loc_02.id, + 'product_id': self.product.id, + 'product_uom_id': self.product.uom_id.id, + 'qty_done': 4, + 'picking_id': receipt.id, + })] + receipt.button_validate() + + data = self.env['vendor.delay.report'].read_group( + [('partner_id', '=', self.partner.id)], + ['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'], + ['product_id'], + )[0] + self.assertEqual(data['qty_on_time'], 10) + self.assertEqual(data['qty_total'], 10) + self.assertEqual(data['on_time_rate'], 100) + + def test_vendor_delay_report_with_backorder(self): + """ + PO 10 units x P + Receive 6 x P with backorder + -> 60% received + Process the backorder + -> 100% received + """ + po_form = Form(self.env['purchase.order']) + po_form.partner_id = self.partner + with po_form.order_line.new() as line: + line.product_id = self.product + line.product_qty = 10 + po = po_form.save() + po.button_confirm() + + receipt01 = po.picking_ids + receipt01_move = receipt01.move_ids + receipt01_move.quantity_done = 6 + action = receipt01.button_validate() + Form(self.env[action['res_model']].with_context(action['context'])).save().process() + + data = self.env['vendor.delay.report'].read_group( + [('partner_id', '=', self.partner.id)], + ['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'], + ['product_id'], + )[0] + self.assertEqual(data['qty_on_time'], 6) + self.assertEqual(data['qty_total'], 10) + self.assertEqual(data['on_time_rate'], 60) + + receipt02 = receipt01.backorder_ids + receipt02.move_ids.quantity_done = 4 + receipt02.button_validate() + + (receipt01 | receipt02).move_ids.invalidate_recordset() + data = self.env['vendor.delay.report'].read_group( + [('partner_id', '=', self.partner.id)], + ['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'], + ['product_id'], + )[0] + self.assertEqual(data['qty_on_time'], 10) + self.assertEqual(data['qty_total'], 10) + self.assertEqual(data['on_time_rate'], 100) + + def test_vendor_delay_report_without_backorder(self): + """ + PO 10 units x P + Receive 6 x P without backorder + -> 60% received + """ + po_form = Form(self.env['purchase.order']) + po_form.partner_id = self.partner + with po_form.order_line.new() as line: + line.product_id = self.product + line.product_qty = 10 + po = po_form.save() + po.button_confirm() + + receipt01 = po.picking_ids + receipt01_move = receipt01.move_ids + receipt01_move.quantity_done = 6 + action = receipt01.button_validate() + Form(self.env[action['res_model']].with_context(action['context'])).save().process_cancel_backorder() + + data = self.env['vendor.delay.report'].read_group( + [('partner_id', '=', self.partner.id)], + ['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'], + ['product_id'], + )[0] + self.assertEqual(data['qty_on_time'], 6) + self.assertEqual(data['qty_total'], 10) + self.assertEqual(data['on_time_rate'], 60)