diff --git a/addons/purchase_stock/tests/test_reordering_rule.py b/addons/purchase_stock/tests/test_reordering_rule.py index 722de3b3c16..a6b5512574a 100644 --- a/addons/purchase_stock/tests/test_reordering_rule.py +++ b/addons/purchase_stock/tests/test_reordering_rule.py @@ -974,6 +974,59 @@ class TestReorderingRule(TransactionCase): self.assertNotEqual(po05, po04, 'A new PO should be generated') self.assertEqual(po05.order_line.product_id, product_02) + def test_reordering_rule_visibility_days(self): + """ + Test the visibility days on the reordering rule update the qty_to_order but do not + update the forecasted quantity of the current day. + + ex: + - We are January 14th + - visibility days = 10 + - A sale order is scheduled on January 20th + -> 2 scenarios + 1. Today's forecasted quantity is < orderpoint's min qty + the sale order will be taken into account in the forecasted quantity + 2. Todays's forecasted quantity is >= orderpoint's min qty + the sale order will not be taken into account in the forecasted quantity + """ + # create reordering rule + wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) + op = self.env['stock.warehouse.orderpoint'].create({ + 'warehouse_id': wh.id, + 'location_id': wh.lot_stock_id.id, + 'product_id': self.product_01.id, + 'product_min_qty': 0, + 'product_max_qty': 0, + 'visibility_days': 10, + }) + + # out move on January 20th + move = self.env['stock.move'].create({ + 'name': 'Test move', + 'product_id': self.product_01.id, + 'product_uom': self.product_01.uom_id.id, + 'product_uom_qty': 1, + 'location_id': wh.lot_stock_id.id, + 'location_dest_id': self.env.ref('stock.stock_location_customers').id, + 'date': dt.today() + td(days=6), + }) + move._action_confirm() + self.assertEqual(op.qty_to_order, 0, 'sale order is ignored') + # out move today to force the forecast to be negative + move = self.env['stock.move'].create({ + 'name': 'Test move', + 'product_id': self.product_01.id, + 'product_uom': self.product_01.uom_id.id, + 'product_uom_qty': 1, + 'location_id': wh.lot_stock_id.id, + 'location_dest_id': self.env.ref('stock.stock_location_customers').id, + }) + move._action_confirm() + + #virtual available is -1 but we need to replenish 2 + self.product_01.virtual_available = -1 + self.assertEqual(op.qty_to_order, 2, 'sale order is ignored') + def test_update_po_line_without_purchase_access_right(self): """ Test that a user without purchase access right can update a PO line from picking.""" # create a user with only inventory access right diff --git a/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py b/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py index d5c6c1b04e6..ea2a928f526 100644 --- a/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py +++ b/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py @@ -2,8 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests.common import TransactionCase, Form -from freezegun import freeze_time -from datetime import datetime, timedelta + class TestSalePurchaseStockFlow(TransactionCase): @@ -85,60 +84,3 @@ class TestSalePurchaseStockFlow(TransactionCase): sm.move_line_ids.quantity = 10 self.assertEqual(so.order_line.qty_delivered, 10) - - @freeze_time('2024-01-01') - def test_reordering_with_visibility_days(self): - """ - If reordering rules' visibility is set bigger than - DAYS_FROM_TODAY_TO_ORDER (plus lead time). Then the - order should be included in the calculation of quantity to order. - - ┌─ Today ┌── Scheduled Delivery - │ (2024-01-01) │ (2024-02-01) - │ │ aka commitment_date - │ │ - ▼ ▼ - ──────────────────────────────────────────► - time - ◄────────────────────────────► - DAYS_FROM_TODAY_TO_ORDER - - ◄────► - lead_time - """ - N_ORDERED_QTY = 666 - DAYS_FROM_TODAY_TO_ORDER = 30 - MONTH_FROM_TODAY = (datetime.today() + timedelta(days=DAYS_FROM_TODAY_TO_ORDER)).strftime('%Y-%m-%d') - - # Setup: Create a product with vendor - partner = self.env['res.partner'].create({'name': 'Azure Interior'}) - seller = self.env['product.supplierinfo'].create({ - 'partner_id': partner.id, - 'price': 1.0, - }) - product = self.env['product.product'].create({ - 'name': 'Dummy Product', - 'type': 'product', - 'seller_ids': [seller.id], - }) - - # Setup: Create sale order scheduled in the future - so = self.env['sale.order'].create({ - 'partner_id': self.customer.id, - 'commitment_date': MONTH_FROM_TODAY, - 'order_line': [(0, 0, { - 'name': product.name, - 'product_id': product.id, - 'price_unit': 1, - 'product_uom_qty': N_ORDERED_QTY, - })], - }) - so.action_confirm() # so.state: 'draft' -> 'sale' - - # Create Reordering rule and trigger recalculation - orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) - orderpoint_form.product_id = product - orderpoint_form.visibility_days = DAYS_FROM_TODAY_TO_ORDER - orderpoint = orderpoint_form.save() - - self.assertEqual(orderpoint.qty_to_order, N_ORDERED_QTY, f"Order from {DAYS_FROM_TODAY_TO_ORDER} days from today NOT included into the qty_to_order calculation, despite having visibility days set {DAYS_FROM_TODAY_TO_ORDER}!") diff --git a/addons/stock/models/stock_orderpoint.py b/addons/stock/models/stock_orderpoint.py index d9a523893b1..59326cf7896 100644 --- a/addons/stock/models/stock_orderpoint.py +++ b/addons/stock/models/stock_orderpoint.py @@ -277,11 +277,12 @@ class StockWarehouseOrderpoint(models.Model): continue qty_to_order = 0.0 rounding = orderpoint.product_uom.rounding - # We want to know how much we should order to also satisfy the needs that gonna appear in the next (visibility) days - product_context = orderpoint._get_product_context(visibility_days=orderpoint.visibility_days) - qty_forecast_with_visibility = orderpoint.product_id.with_context(product_context).read(['virtual_available'])[0]['virtual_available'] + orderpoint._quantity_in_progress()[orderpoint.id] - - if float_compare(qty_forecast_with_visibility, orderpoint.product_min_qty, precision_rounding=rounding) < 0: + # The check is on purpose. We only want to consider the visibility days if the forecast is negative and + # there is a already something to ressuply base on lead times. + if float_compare(orderpoint.qty_forecast, orderpoint.product_min_qty, precision_rounding=rounding) < 0: + # We want to know how much we should order to also satisfy the needs that gonna appear in the next (visibility) days + product_context = orderpoint._get_product_context(visibility_days=orderpoint.visibility_days) + qty_forecast_with_visibility = orderpoint.product_id.with_context(product_context).read(['virtual_available'])[0]['virtual_available'] + orderpoint._quantity_in_progress()[orderpoint.id] qty_to_order = max(orderpoint.product_min_qty, orderpoint.product_max_qty) - qty_forecast_with_visibility remainder = orderpoint.qty_multiple > 0.0 and qty_to_order % orderpoint.qty_multiple or 0.0 if (float_compare(remainder, 0.0, precision_rounding=rounding) > 0