diff --git a/addons/sale_purchase_stock/tests/__init__.py b/addons/sale_purchase_stock/tests/__init__.py
index 54b0c0958fd..32d6b905302 100644
--- a/addons/sale_purchase_stock/tests/__init__.py
+++ b/addons/sale_purchase_stock/tests/__init__.py
@@ -3,3 +3,4 @@
from . import test_sale_purchase_stock_flow
from . import test_access_rights
+from . import test_unwanted_replenish_flow
diff --git a/addons/sale_purchase_stock/tests/test_unwanted_replenish_flow.py b/addons/sale_purchase_stock/tests/test_unwanted_replenish_flow.py
new file mode 100644
index 00000000000..95c8d668f74
--- /dev/null
+++ b/addons/sale_purchase_stock/tests/test_unwanted_replenish_flow.py
@@ -0,0 +1,135 @@
+from datetime import datetime, timedelta
+from odoo import Command
+from odoo.tests import common, Form, tagged
+
+@tagged('post_install', '-at_install')
+class TestWarnUnwantedReplenish(common.TransactionCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ cls.buy_route = cls.env.ref('purchase_stock.route_warehouse0_buy')
+
+ # Create a vendor (& suppliers) and a customer
+ cls.vendor = cls.env['res.partner'].create(dict(name='Vendor'))
+ cls.customer = cls.env['res.partner'].create(dict(name='Customer'))
+
+ cls.supplier_A = cls.env['product.supplierinfo'].create({
+ 'partner_id' : cls.vendor.id,
+ 'min_qty' : 0.0,
+ 'price' : 10.0,
+ 'delay' : 0
+ })
+
+ cls.supplier_B = cls.env['product.supplierinfo'].create({
+ 'partner_id' : cls.vendor.id,
+ 'min_qty' : 0.0,
+ 'price' : 12.0,
+ 'delay' : 0
+ })
+
+ # Create a "A" and a "B" Product :
+ # No Stock
+ # Partner/Customer Lead Time = 0
+ # Manual reordering 0 0
+
+ cls.product_A = cls.env['product.product'].create({
+ 'name': 'Product A',
+ 'type': 'product',
+ 'categ_id': cls.env.ref('product.product_category_all').id,
+ 'purchase_method': 'purchase',
+ 'invoice_policy': 'delivery',
+ 'standard_price': 5.0,
+ 'list_price': 10.0,
+ 'seller_ids': [Command.link(cls.supplier_A.id)],
+ 'route_ids': [Command.link(cls.buy_route.id)],
+ 'sale_delay' : 0,
+ })
+
+ cls.product_B = cls.env['product.product'].create({
+ 'name': 'Product B',
+ 'type': 'product',
+ 'categ_id': cls.env.ref('product.product_category_all').id,
+ 'purchase_method': 'purchase',
+ 'invoice_policy': 'delivery',
+ 'standard_price': 6.0,
+ 'list_price': 12.0,
+ 'seller_ids': [Command.link(cls.supplier_B.id)],
+ 'route_ids': [Command.link(cls.buy_route.id)],
+ 'sale_delay': 0,
+ })
+
+
+ orderpoint_form = Form(cls.env['stock.warehouse.orderpoint'])
+ orderpoint_form.product_id = cls.product_A
+ orderpoint_form.product_min_qty = 0.0
+ orderpoint_form.product_max_qty = 1.0
+ cls.orderpoint_A = orderpoint_form.save()
+ cls.orderpoint_A.trigger = 'manual'
+
+ orderpoint_form = Form(cls.env['stock.warehouse.orderpoint'])
+ orderpoint_form.product_id = cls.product_B
+ orderpoint_form.product_min_qty = 0.0
+ orderpoint_form.product_max_qty = 1.0
+ cls.orderpoint_B = orderpoint_form.save()
+ cls.orderpoint_B.trigger = 'manual'
+
+ # Create Sales
+ # For A and for B
+ # Delivered today
+ # Confirm SO
+
+ cls.sale_order = cls.env['sale.order'].create({
+ 'partner_id': cls.customer.id,
+ 'order_line': [
+ Command.create({
+ 'product_id': cls.product_A.id,
+ 'product_uom_qty': 10,
+ }),
+ Command.create({
+ 'product_id': cls.product_B.id,
+ 'product_uom_qty': 10,
+ }),
+ ],
+ })
+
+ cls.sale_order.action_confirm()
+
+ # Create PO for Product A
+ # Confirm PO with date planned : TODAY
+ # Incoming Picking : reschedule in one week
+
+ cls.po_A = cls.env['purchase.order'].create({
+ 'partner_id': cls.vendor.id,
+ 'order_line': [
+ Command.create({
+ 'name': cls.product_A.name,
+ 'product_id': cls.product_A.id,
+ 'product_qty': 10.0,
+ 'price_unit': 10.0,
+ 'date_planned': datetime.today(),
+ })],
+ })
+
+ cls.po_A.button_confirm()
+
+ cls.picking_A = cls.po_A.picking_ids[0]
+ cls.picking_A.scheduled_date = (datetime.today() + timedelta(days=10))
+
+ def test_01_pre_updateA_post(self):
+ """
+ TEST 1
+ Replenishment ->
+ Product A
+ unwanted_replenish SHALL be TRUE
+ Product B
+ unwanted_replenish SHALL be FALSE
+ Product A
+ Modify Visible Days past 1 Week -> unwanted_replenish SHALL be FALSE
+ """
+ self.assertTrue(self.orderpoint_A.unwanted_replenish, 'Orderpoint A not set to unwanted_replenish')
+ self.assertFalse(self.orderpoint_B.unwanted_replenish, 'Orderpoint B is set to unwanted_replenish')
+ #Update Orderpoint A
+ self.orderpoint_A.visibility_days = 10
+ self.assertFalse(self.orderpoint_A.unwanted_replenish, 'Orderpoint A shall not be set to unwanted_replenish')
diff --git a/addons/stock/models/stock_orderpoint.py b/addons/stock/models/stock_orderpoint.py
index d852d160f8b..6f2f5632c0e 100644
--- a/addons/stock/models/stock_orderpoint.py
+++ b/addons/stock/models/stock_orderpoint.py
@@ -11,7 +11,7 @@ from odoo import SUPERUSER_ID, _, api, fields, models, registry
from odoo.addons.stock.models.stock_rule import ProcurementException
from odoo.exceptions import RedirectWarning, UserError, ValidationError
from odoo.osv import expression
-from odoo.tools import float_compare, frozendict, split_every
+from odoo.tools import float_compare, float_is_zero, frozendict, split_every
_logger = logging.getLogger(__name__)
@@ -94,6 +94,8 @@ class StockWarehouseOrderpoint(models.Model):
help="Consider product forecast these many days in the future upon product replenishment, set to 0 for just-in-time.\n"
"The value depends on the type of the route (Buy or Manufacture)")
+ unwanted_replenish = fields.Boolean('Unwanted Replenish', compute="_compute_unwanted_replenish")
+
_sql_constraints = [
('qty_multiple_check', 'CHECK( qty_multiple >= 0 )', 'Qty Multiple must be greater than or equal to zero.'),
('product_location_check', 'unique (product_id, location_id, company_id)', 'A replenishment rule already exists for this product on this location.'),
@@ -169,6 +171,15 @@ class StockWarehouseOrderpoint(models.Model):
], limit=1)
orderpoint.location_id = warehouse.lot_stock_id.id
+ @api.depends('product_id', 'qty_to_order', 'product_max_qty')
+ def _compute_unwanted_replenish(self):
+ for orderpoint in self:
+ if not orderpoint.product_id or float_is_zero(orderpoint.qty_to_order, precision_rounding=orderpoint.product_uom.rounding) or float_is_zero(orderpoint.product_max_qty, precision_rounding=orderpoint.product_uom.rounding):
+ orderpoint.unwanted_replenish = False
+ else:
+ after_replenish_qty = orderpoint.product_id.with_context(company_id=orderpoint.company_id.id, location=orderpoint.location_id.id).virtual_available + orderpoint.qty_to_order
+ orderpoint.unwanted_replenish = float_compare(after_replenish_qty, orderpoint.product_max_qty, precision_rounding=orderpoint.product_uom.rounding) > 0
+
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
diff --git a/addons/stock/views/stock_orderpoint_views.xml b/addons/stock/views/stock_orderpoint_views.xml
index 008d6b3c60d..af72498f734 100644
--- a/addons/stock/views/stock_orderpoint_views.xml
+++ b/addons/stock/views/stock_orderpoint_views.xml
@@ -43,12 +43,14 @@
+
-
+
+