diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py
index de6debd..b8a3a21 100755
--- a/modules/purchase_trade/purchase.py
+++ b/modules/purchase_trade/purchase.py
@@ -1579,6 +1579,24 @@ class Line(metaclass=PoolMeta):
def get_targeted_qt(self, name):
return self._tolerance_actual_quantity()
+ def check_targeted_qt_tolerance(self):
+ if not getattr(self, 'lots', None):
+ return
+ theoretical = self._tolerance_theoretical_quantity()
+ if not theoretical:
+ return
+ targeted = self._tolerance_actual_quantity()
+ tol_min = -self.get_tolerance_min('tolerance_min')
+ tol_max = self.get_tolerance_max('tolerance_max')
+ min_qt = theoretical * (Decimal(1) - tol_min / Decimal(100))
+ max_qt = theoretical * (Decimal(1) + tol_max / Decimal(100))
+ if targeted < min_qt:
+ raise UserError(
+ "Targeted Qt is below the tolerance minimum.")
+ if targeted > max_qt:
+ raise UserError(
+ "Targeted Qt exceeds the tolerance maximum.")
+
def get_progress(self,name):
PS = Pool().get('purchase.pricing.summary')
ps = PS.search(['line','=',self.id])
@@ -1950,9 +1968,10 @@ class Line(metaclass=PoolMeta):
if line.price_components:
for pc in line.price_components:
if pc.triggers:
- for tr in pc.triggers:
- line.check_from_to(tr)
+ for tr in pc.triggers:
+ line.check_from_to(tr)
line.check_pricing()
+ line.check_targeted_qt_tolerance()
#no lot need to create one with line quantity
if not line.created_by_code:
if cls._sync_initial_quantity_from_theorical(line):
diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py
index cfaea61..32a458d 100755
--- a/modules/purchase_trade/sale.py
+++ b/modules/purchase_trade/sale.py
@@ -1569,6 +1569,24 @@ class SaleLine(metaclass=PoolMeta):
def get_targeted_qt(self, name):
return self._tolerance_actual_quantity()
+ def check_targeted_qt_tolerance(self):
+ if not getattr(self, 'lots', None):
+ return
+ theoretical = self._tolerance_theoretical_quantity()
+ if not theoretical:
+ return
+ targeted = self._tolerance_actual_quantity()
+ tol_min = -self.get_tolerance_min('tolerance_min')
+ tol_max = self.get_tolerance_max('tolerance_max')
+ min_qt = theoretical * (Decimal(1) - tol_min / Decimal(100))
+ max_qt = theoretical * (Decimal(1) + tol_max / Decimal(100))
+ if targeted < min_qt:
+ raise UserError(
+ "Targeted Qt is below the tolerance minimum.")
+ if targeted > max_qt:
+ raise UserError(
+ "Targeted Qt exceeds the tolerance maximum.")
+
def get_progress(self,name):
PS = Pool().get('sale.pricing.summary')
ps = PS.search(['sale_line','=',self.id])
@@ -2071,6 +2089,7 @@ class SaleLine(metaclass=PoolMeta):
for tr in pc.triggers:
line.check_from_to(tr)
line.check_pricing()
+ line.check_targeted_qt_tolerance()
#no lot need to create one with line quantity
logger.info("FROM_VALIDATE_LINE:%s",line.created_by_code)
if not line.created_by_code:
diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py
index eff3f23..70d5164 100644
--- a/modules/purchase_trade/tests/test_module.py
+++ b/modules/purchase_trade/tests/test_module.py
@@ -2965,6 +2965,43 @@ class PurchaseTradeTestCase(ModuleTestCase):
line.get_tolerance_used('tolerance_used'),
Decimal('-5.00000'))
+ def test_purchase_line_targeted_qt_blocks_above_tolerance(self):
+ 'purchase line targeted quantity must stay inside tolerance bounds'
+ physical = Mock(lot_type='physic')
+ physical.get_current_quantity_converted.return_value = Decimal('111')
+ line = purchase_module.Line()
+ line.quantity_theorical = Decimal('100')
+ line.quantity = Decimal('100')
+ line.lots = [physical]
+ line.inherit_tol = False
+ line.tol_min = Decimal('5')
+ line.tol_max = Decimal('10')
+
+ with self.assertRaises(UserError):
+ line.check_targeted_qt_tolerance()
+
+ def test_sale_line_targeted_qt_blocks_below_tolerance(self):
+ 'sale line targeted quantity must not fall below tolerance minimum'
+ unit = Mock()
+ virtual = Mock(id=20, lot_type='virtual')
+ line = sale_module.SaleLine()
+ line.unit = unit
+ line.quantity_theorical = Decimal('100')
+ line.quantity = Decimal('100')
+ line.lots = [virtual]
+ line.inherit_tol = False
+ line.tol_min = Decimal('5')
+ line.tol_max = Decimal('10')
+ lot_qt_model = Mock()
+ lot_qt_model.search.return_value = [
+ Mock(lot_quantity=Decimal('-94'), lot_unit=unit)]
+ pool = Mock()
+ pool.get.return_value = lot_qt_model
+
+ with patch.object(sale_module, 'Pool', return_value=pool):
+ with self.assertRaises(UserError):
+ line.check_targeted_qt_tolerance()
+
def test_lot_context_group_defaults_to_all(self):
'lots management opens without grouping by physical lot'
self.assertEqual(lot_module.LotContext.default_group(), 'all')
diff --git a/modules/purchase_trade/view/purchase_line_form.xml b/modules/purchase_trade/view/purchase_line_form.xml
index 7a4bcad..a6d725f 100755
--- a/modules/purchase_trade/view/purchase_line_form.xml
+++ b/modules/purchase_trade/view/purchase_line_form.xml
@@ -47,9 +47,9 @@ this repository contains the full copyright notices and license terms. -->
+ width="360" xexpand="0" xfill="0"/>
-
+
diff --git a/modules/purchase_trade/view/sale_line_form.xml b/modules/purchase_trade/view/sale_line_form.xml
index 81de5dc..106c66d 100755
--- a/modules/purchase_trade/view/sale_line_form.xml
+++ b/modules/purchase_trade/view/sale_line_form.xml
@@ -47,9 +47,9 @@ this repository contains the full copyright notices and license terms. -->
+ width="360" xexpand="0" xfill="0"/>
-
+