From ff75b9d4f4291598cb354fa785c6258028d56bf3 Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Tue, 14 Feb 2023 13:43:12 +0000 Subject: [PATCH] [FIX] purchase_stock: add POL to existing PO if draft To reproduce the issue: 1. In Settings: - Days to Purchase: 10 2. Create two products P01, P02: - Storable - With the same seller - Delivery Lead Time: 1.0 3. On replenishment page, create a new line: - Product: P01 - Min Qty: 1.0 4. Order once - a RfQ is created and the order deadline is in ten days 5. Time travel to the next day 6. Repeat steps 3-4 with P02 Error: a second RfQ has been generated. However, as the first one is still in draft state, the POL of P02 should be added to that first RfQ When processing the orderpoint of P02, at some point, we try to find an existing RfQ with its order deadline equal to `today + 10 days`. Since we are the next day (step 5), the order deadline of the first RfQ is `today + 9 days`, so we don't find it. OPW-3047931 closes odoo/odoo#113982 X-original-commit: 8bf7f9038915be520488b2602c20929ca115a033 Signed-off-by: Arnold Moyaux (arm) Signed-off-by: Adrien Widart --- addons/purchase_stock/models/stock_rule.py | 5 +- .../tests/test_purchase_lead_time.py | 13 ++- .../tests/test_reordering_rule.py | 86 +++++++++++++++++++ 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/addons/purchase_stock/models/stock_rule.py b/addons/purchase_stock/models/stock_rule.py index 499d0a3bc75..475de4e5e4c 100644 --- a/addons/purchase_stock/models/stock_rule.py +++ b/addons/purchase_stock/models/stock_rule.py @@ -305,9 +305,10 @@ class StockRule(models.Model): ('company_id', '=', company_id.id), ('user_id', '=', False), ) - if values.get('orderpoint_id'): + delta_days = self.env['ir.config_parameter'].sudo().get_param('purchase_stock.delta_days_merge') + if values.get('orderpoint_id') and delta_days is not False: procurement_date = fields.Date.to_date(values['date_planned']) - relativedelta(days=int(values['supplier'].delay)) - delta_days = int(self.env['ir.config_parameter'].sudo().get_param('purchase_stock.delta_days_merge') or 0) + delta_days = int(delta_days) domain += ( ('date_order', '<=', datetime.combine(procurement_date + relativedelta(days=delta_days), datetime.max.time())), ('date_order', '>=', datetime.combine(procurement_date - relativedelta(days=delta_days), datetime.min.time())) diff --git a/addons/purchase_stock/tests/test_purchase_lead_time.py b/addons/purchase_stock/tests/test_purchase_lead_time.py index 852b5501b3f..f15827f2b45 100644 --- a/addons/purchase_stock/tests/test_purchase_lead_time.py +++ b/addons/purchase_stock/tests/test_purchase_lead_time.py @@ -344,7 +344,8 @@ class TestPurchaseLeadTime(PurchaseTestCommon): delivery_moves._action_confirm() self.env['procurement.group'].run_scheduler() po_line = self.env['purchase.order.line'].search([('product_id', '=', product.id)]) - self.assertEqual(fields.Date.to_date(po_line.order_id.date_order), fields.Date.today() + timedelta(days=2)) + expected_date_order = fields.Date.today() + timedelta(days=2) + self.assertEqual(fields.Date.to_date(po_line.order_id.date_order), expected_date_order) self.assertEqual(len(po_line), 1) self.assertEqual(po_line.product_uom_qty, 25.0) self.assertEqual(len(po_line.order_id), 1) @@ -354,12 +355,10 @@ class TestPurchaseLeadTime(PurchaseTestCommon): self.mock_date.today.return_value = fields.Date.today() + timedelta(days=2) orderpoint._compute_qty() self.env['procurement.group'].run_scheduler() - po_line = self.env['purchase.order.line'].search([('product_id', '=', product.id)]) - self.assertEqual(len(po_line), 2) - self.assertEqual(len(po_line.order_id), 2) - new_order = po_line.order_id.sorted('date_order')[-1] - self.assertEqual(fields.Date.to_date(new_order.date_order), fields.Date.today() + timedelta(days=2)) - self.assertEqual(new_order.order_line.product_uom_qty, 5.0) + po_line02 = self.env['purchase.order.line'].search([('product_id', '=', product.id)]) + self.assertEqual(po_line02, po_line, 'The orderpoint execution should not create a new POL') + self.assertEqual(fields.Date.to_date(po_line.order_id.date_order), expected_date_order, 'The Order Deadline should not change') + self.assertEqual(po_line.product_uom_qty, 30.0, 'The existing POL should be updated with the quantity of the last execution') def test_supplier_lead_time(self): """ Basic stock configuration and a supplier with a minimum qty and a lead time """ diff --git a/addons/purchase_stock/tests/test_reordering_rule.py b/addons/purchase_stock/tests/test_reordering_rule.py index 028a0ca1fec..0471ea81a77 100644 --- a/addons/purchase_stock/tests/test_reordering_rule.py +++ b/addons/purchase_stock/tests/test_reordering_rule.py @@ -824,3 +824,89 @@ class TestReorderingRule(TransactionCase): {'location_id': supplier_location_id, 'location_dest_id': input_location_id, 'product_qty': 1}, {'location_id': input_location_id, 'location_dest_id': stock_location_id, 'product_qty': 1}, ]) + + def test_add_line_to_existing_draft_po(self): + """ + Days to purchase = 10 + Two products P1, P2 from the same supplier + Several use cases, each time we run the RR one by one. Then, according + to the dates and the configuration, it should use the existing PO or not + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) + + self.env.company.days_to_purchase = 10 + expected_order_date = dt.combine(dt.today() + td(days=10), dt.min.time()) + expected_delivery_date = expected_order_date + td(days=1.0) + # expected_delivery_date = expected_delivery_date.replace(hour=12, minute=0, second=0) + + product_02 = self.env['product.product'].create({ + 'name': 'Super Product', + 'type': 'product', + 'seller_ids': [(0, 0, {'partner_id': self.partner.id})], + }) + + op_01, op_02 = self.env['stock.warehouse.orderpoint'].create([{ + 'warehouse_id': warehouse.id, + 'location_id': warehouse.lot_stock_id.id, + 'product_id': p.id, + 'product_min_qty': 1, + 'product_max_qty': 0, + } for p in [self.product_01, product_02]]) + + op_01.action_replenish() + po01 = self.env['purchase.order'].search([], order='id desc', limit=1) + self.assertEqual(po01.date_order, expected_order_date) + + op_02.action_replenish() + self.assertEqual(po01.date_order, expected_order_date) + self.assertRecordValues(po01.order_line, [ + {'product_id': self.product_01.id, 'date_planned': expected_delivery_date}, + {'product_id': product_02.id, 'date_planned': expected_delivery_date}, + ]) + + # Reset and try another flow + po01.button_cancel() + op_01.action_replenish() + po02 = self.env['purchase.order'].search([], order='id desc', limit=1) + self.assertNotEqual(po02, po01) + + with freeze_time(dt.today() + td(days=1)): + op_02.invalidate_recordset(fnames=['lead_days_date']) + op_02.action_replenish() + self.assertEqual(po02.date_order, expected_order_date) + self.assertRecordValues(po02.order_line, [ + {'product_id': self.product_01.id, 'date_planned': expected_delivery_date}, + {'product_id': product_02.id, 'date_planned': expected_delivery_date + td(days=1)}, + ]) + + # Restrict the merge with POs that have their order deadline in [today - 2 days, today + 2 days] + self.env['ir.config_parameter'].set_param('purchase_stock.delta_days_merge', '2') + + # Reset and try with a second RR executed in the dates range (-> should still use the existing PO) + po02.button_cancel() + op_01.action_replenish() + po03 = self.env['purchase.order'].search([], order='id desc', limit=1) + self.assertNotEqual(po03, po02) + + with freeze_time(dt.today() + td(days=2)): + op_02.invalidate_recordset(fnames=['lead_days_date']) + op_02.action_replenish() + self.assertEqual(po03.date_order, expected_order_date) + self.assertRecordValues(po03.order_line, [ + {'product_id': self.product_01.id, 'date_planned': expected_delivery_date}, + {'product_id': product_02.id, 'date_planned': expected_delivery_date + td(days=2)}, + ]) + + # Reset and try with a second RR executed after the dates range (-> should not use the existing PO) + po03.button_cancel() + op_01.action_replenish() + po04 = self.env['purchase.order'].search([], order='id desc', limit=1) + self.assertNotEqual(po04, po03) + + with freeze_time(dt.today() + td(days=3)): + op_02.invalidate_recordset(fnames=['lead_days_date']) + op_02.action_replenish() + self.assertEqual(po04.order_line.product_id, self.product_01, 'There should be only a line for product 01') + po05 = self.env['purchase.order'].search([], order='id desc', limit=1) + self.assertNotEqual(po05, po04, 'A new PO should be generated') + self.assertEqual(po05.order_line.product_id, product_02)