From 98e451917b5a05eb8d2dc353717be9bf1dd35db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Pigeon?= Date: Mon, 2 Oct 2017 22:34:17 +0200 Subject: [PATCH] [FIX] fix orderpoint compute with purchase multiple --- .../models/procurement_order.py | 36 ++++++ .../tests/test_procurement_order.py | 114 ++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/purchase_packaging/models/procurement_order.py b/purchase_packaging/models/procurement_order.py index 589aed429..e48704fc8 100644 --- a/purchase_packaging/models/procurement_order.py +++ b/purchase_packaging/models/procurement_order.py @@ -2,6 +2,7 @@ # Copyright 2015-2017 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import api, fields, models +from odoo.tools import float_compare class ProcurementOrder(models.Model): @@ -28,3 +29,38 @@ class ProcurementOrder(models.Model): self.product_qty, new_uom_id) res['product_qty'] = max(qty, seller.min_qty) return res + + +class Orderpoint(models.Model): + _inherit = 'stock.warehouse.orderpoint' + + @api.multi + def subtract_procurements_from_orderpoints(self): + # In this method we need to access the purchase order line quantity + # to correctly evaluate the forecast. + # Imagine a product with a minimum rule of 4 units and a purchase + # multiple of 12. The first run will generate a procurement for 4 Pc + # but a purchase for 12 units. + # Let's change the minimum rule to 5 units. + # The standard subtract_procurements_from_orderpoints will return 4 + # and Odoo will create a procurement for 1 unit which will trigger a + # purchase of 12 due to the multiple. So the original purchase will + # be increased to 24 units which is wrong. + # This override will return 12 and no additionnal procurement will be + # created + res = super(Orderpoint, self).subtract_procurements_from_orderpoints() + for orderpoint in self: + procs = self.env['procurement.order'].search( + [('orderpoint_id', '=', orderpoint.id), + ('state', 'not in', ['cancel', 'done'])]) + if procs: + po_lines = procs.mapped('purchase_line_id').filtered( + lambda x: x.state == 'draft') + if po_lines: + qty = sum([line.product_qty for line in po_lines]) + precision = orderpoint.product_uom.rounding + if float_compare( + qty, res[orderpoint.id], + precision_rounding=precision) >= 0: + res[orderpoint.id] = qty + return res diff --git a/purchase_packaging/tests/test_procurement_order.py b/purchase_packaging/tests/test_procurement_order.py index 584e44f62..e031f06d4 100644 --- a/purchase_packaging/tests/test_procurement_order.py +++ b/purchase_packaging/tests/test_procurement_order.py @@ -259,3 +259,117 @@ class TestProcurementOrder(common.TransactionCase): proc1.purchase_line_id.product_uom) self.assertEqual(36, proc1.purchase_line_id.price_unit) proc1.purchase_id.button_confirm() + + def test_procurement_from_orderpoint_draft_po(self): + # Define a multiple of 12 on supplier info + # Trigger a stock minimum rule of 10 PC + # A purchase line with 12 PC should be generated + # Change the stock minimum to 11 PC + # The purchase quantity should remains 12 + # Change the stock minimum to 13 PC + # The purchase quantity should increase up to 24 + warehouse = self.env.ref('stock.warehouse0') + product = self.env.ref('product.product_product_3') + product.route_ids = [( + 4, self.env.ref("purchase.route_warehouse0_buy").id)] + self.env.ref('product.product_uom_dozen').rounding = 1 + procurement_obj = self.env['procurement.order'] + + self.sp_30.min_qty = 1 + self.sp_30.min_qty_uom_id = self.env.ref('product.product_uom_dozen') + + orderpoint = self.env['stock.warehouse.orderpoint'].create({ + 'warehouse_id': warehouse.id, + 'location_id': warehouse.lot_stock_id.id, + 'product_id': product.id, + 'product_min_qty': 10, + 'product_max_qty': 10, + }) + procurement_obj.run_scheduler() + proc = procurement_obj.search([('orderpoint_id', '=', orderpoint.id)]) + self.assertEqual(len(proc), 1) + self.assertTrue(proc.purchase_line_id) + self.assertEqual(proc.purchase_line_id.product_qty, 12) + + # change order_point level and rerun + orderpoint.product_min_qty = 11 + orderpoint.product_max_qty = 11 + + procurement_obj.run_scheduler() + procs = procurement_obj.search([('orderpoint_id', '=', orderpoint.id)]) + + self.assertTrue(procs) + self.assertEqual(len(procs), 1) + + # change order_point level and rerun + orderpoint.product_min_qty = 13 + orderpoint.product_max_qty = 13 + + procurement_obj.run_scheduler() + procs = procurement_obj.search([('orderpoint_id', '=', orderpoint.id)]) + + self.assertTrue(procs) + self.assertEqual(len(procs), 2) + + for proc in procs: + self.assertTrue(proc.purchase_line_id) + self.assertEqual(proc.purchase_line_id.product_qty, 24) + + def test_procurement_from_orderpoint_confirmed_po(self): + # Define a multiple of 12 on supplier info + # Trigger a stock minimum rule of 10 PC + # A purchase line with 12 PC should be generated + # Confirm the purchase order + # Change the stock minimum to 11 PC + # No new purchase should be generated + # Change the stock minimum to 13 PC + # A new purchase should be generated + warehouse = self.env.ref('stock.warehouse0') + product = self.env.ref('product.product_product_3') + product.route_ids = [( + 4, self.env.ref("purchase.route_warehouse0_buy").id)] + self.env.ref('product.product_uom_dozen').rounding = 1 + procurement_obj = self.env['procurement.order'] + + self.sp_30.min_qty = 1 + self.sp_30.min_qty_uom_id = self.env.ref('product.product_uom_dozen') + + orderpoint = self.env['stock.warehouse.orderpoint'].create({ + 'warehouse_id': warehouse.id, + 'location_id': warehouse.lot_stock_id.id, + 'product_id': product.id, + 'product_min_qty': 10, + 'product_max_qty': 10, + }) + procurement_obj.run_scheduler() + proc = procurement_obj.search([('orderpoint_id', '=', orderpoint.id)]) + self.assertEqual(len(proc), 1) + self.assertTrue(proc.purchase_line_id) + self.assertEqual(proc.purchase_line_id.product_qty, 12) + + proc.purchase_line_id.order_id.button_confirm() + + # change order_point level and rerun + orderpoint.product_min_qty = 11 + orderpoint.product_max_qty = 11 + + procurement_obj.run_scheduler() + proc = procurement_obj.search([('orderpoint_id', '=', orderpoint.id)]) + + self.assertTrue(proc) + self.assertEqual(len(proc), 1) + self.assertEqual(proc.purchase_line_id.product_qty, 12) + + # change order_point level and rerun + orderpoint.product_min_qty = 13 + orderpoint.product_max_qty = 13 + + procurement_obj.run_scheduler() + procs = procurement_obj.search([('orderpoint_id', '=', orderpoint.id)]) + + self.assertTrue(procs) + self.assertEqual(len(procs), 2) + + for proc in procs: + self.assertTrue(proc.purchase_line_id) + self.assertEqual(proc.purchase_line_id.product_qty, 12)