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. -->