diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index 940fb02..2e28d2b 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -445,7 +445,10 @@ class Purchase(metaclass=PoolMeta): getattr(line, 'quantity_theorical', None) or getattr(line, 'quantity', None) or 0)) - actual += self._line_lot_quantity_for_tolerance(line) + if hasattr(line, '_tolerance_targeted_quantity'): + actual += line._tolerance_targeted_quantity() + else: + actual += self._line_lot_quantity_for_tolerance(line) return theoretical, actual def get_tolerance_used(self, name): @@ -1236,13 +1239,14 @@ class Line(metaclass=PoolMeta): def _set_initial_quantity_values(cls, values): if 'quantity_theorical' not in values: return - if not cls._is_empty_quantity(values.get('quantity')): - return quantity = values.get('quantity_theorical') if cls._is_empty_quantity(quantity): return - values['quantity'] = Decimal(str(quantity)).quantize( - Decimal("0.00001")) + quantity = Decimal(str(quantity)).quantize(Decimal("0.00001")) + if cls._is_empty_quantity(values.get('quantity')): + values['quantity'] = quantity + if cls._is_empty_quantity(values.get('targeted_qt')): + values['targeted_qt'] = quantity @classmethod def _set_quantity_counter_values_from_theorical(cls, records, values): @@ -1256,6 +1260,8 @@ class Line(metaclass=PoolMeta): else: quantity = Decimal(str(quantity)).quantize(Decimal("0.00001")) values['quantity'] = quantity + if 'targeted_qt' not in values: + values['targeted_qt'] = quantity @classmethod def _fresh_lines_for_quantity_consistency(cls, lines): @@ -1367,8 +1373,7 @@ class Line(metaclass=PoolMeta): fields.Numeric("Tolerance min"), 'get_tolerance_min') tolerance_max = fields.Function( fields.Numeric("Tolerance max"), 'get_tolerance_max') - targeted_qt = fields.Function( - fields.Numeric("Targeted Qt", digits='unit'), 'get_targeted_qt') + targeted_qt = fields.Numeric("Targeted Qt", digits='unit') # certification = fields.Selection([ # (None, ''), # ('bci', 'BCI'), @@ -1466,11 +1471,12 @@ class Line(metaclass=PoolMeta): return " | ".join(filter(None, values)) @fields.depends( - 'quantity_theorical', 'quantity', 'lots', + 'quantity_theorical', 'quantity', 'lots', 'targeted_qt', methods=['_recompute_trade_price_fields']) def on_change_quantity_theorical(self): if self._sync_quantity_counter_from_theorical(self): self._recompute_trade_price_fields() + self.targeted_qt = self._tolerance_theoretical_quantity() @classmethod def default_price_type(cls): @@ -1556,12 +1562,19 @@ class Line(metaclass=PoolMeta): unit) return quantity + def _tolerance_targeted_quantity(self): + targeted = getattr(self, 'targeted_qt', None) + if not self._is_empty_quantity(targeted): + return Decimal(str(targeted)) + return self._tolerance_theoretical_quantity() + + @fields.depends('targeted_qt', 'quantity_theorical', 'quantity') def get_tolerance_used(self, name): theoretical = self._tolerance_theoretical_quantity() if not theoretical: return Decimal(0) return round( - ((self._tolerance_actual_quantity() - theoretical) + ((self._tolerance_targeted_quantity() - theoretical) / theoretical) * Decimal(100), 5) def get_tolerance_min(self, name): @@ -1576,16 +1589,13 @@ class Line(metaclass=PoolMeta): return Decimal(str(purchase.tol_max or 0)) return Decimal(str(self.tol_max or 0)) - 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() + if self._is_empty_quantity(getattr(self, 'targeted_qt', None)): + self.targeted_qt = theoretical + targeted = self._tolerance_targeted_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)) diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 32a458d..9e66a4e 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -409,7 +409,10 @@ class Sale(metaclass=PoolMeta): getattr(line, 'quantity_theorical', None) or getattr(line, 'quantity', None) or 0))) - actual += self._line_lot_quantity_for_tolerance(line) + if hasattr(line, '_tolerance_targeted_quantity'): + actual += line._tolerance_targeted_quantity() + else: + actual += self._line_lot_quantity_for_tolerance(line) return theoretical, actual def get_tolerance_used(self, name): @@ -1195,13 +1198,14 @@ class SaleLine(metaclass=PoolMeta): def _set_initial_quantity_values(cls, values): if 'quantity_theorical' not in values: return - if not cls._is_empty_quantity(values.get('quantity')): - return quantity = values.get('quantity_theorical') if cls._is_empty_quantity(quantity): return - values['quantity'] = Decimal(str(quantity)).quantize( - Decimal("0.00001")) + quantity = Decimal(str(quantity)).quantize(Decimal("0.00001")) + if cls._is_empty_quantity(values.get('quantity')): + values['quantity'] = quantity + if cls._is_empty_quantity(values.get('targeted_qt')): + values['targeted_qt'] = quantity @classmethod def _set_quantity_counter_values_from_theorical(cls, records, values): @@ -1215,6 +1219,8 @@ class SaleLine(metaclass=PoolMeta): else: quantity = Decimal(str(quantity)).quantize(Decimal("0.00001")) values['quantity'] = quantity + if 'targeted_qt' not in values: + values['targeted_qt'] = quantity @classmethod def _fresh_lines_for_quantity_consistency(cls, lines): @@ -1349,8 +1355,7 @@ class SaleLine(metaclass=PoolMeta): fields.Numeric("Tolerance min"), 'get_tolerance_min') tolerance_max = fields.Function( fields.Numeric("Tolerance max"), 'get_tolerance_max') - targeted_qt = fields.Function( - fields.Numeric("Targeted Qt", digits='unit'), 'get_targeted_qt') + targeted_qt = fields.Numeric("Targeted Qt", digits='unit') certif = fields.Many2One('purchase.certification',"Certification",states={'readonly': (Eval('inherit_cer')),}) # certification = fields.Selection([ # (None, ''), @@ -1453,11 +1458,12 @@ class SaleLine(metaclass=PoolMeta): return " | ".join(filter(None, values)) @fields.depends( - 'quantity_theorical', 'quantity', 'lots', + 'quantity_theorical', 'quantity', 'lots', 'targeted_qt', methods=['_recompute_trade_price_fields']) def on_change_quantity_theorical(self): if self._sync_quantity_counter_from_theorical(self): self._recompute_trade_price_fields() + self.targeted_qt = self._tolerance_theoretical_quantity() @classmethod def default_price_type(cls): @@ -1546,12 +1552,19 @@ class SaleLine(metaclass=PoolMeta): unit) return quantity + def _tolerance_targeted_quantity(self): + targeted = getattr(self, 'targeted_qt', None) + if not self._is_empty_quantity(targeted): + return abs(Decimal(str(targeted))) + return self._tolerance_theoretical_quantity() + + @fields.depends('targeted_qt', 'quantity_theorical', 'quantity') def get_tolerance_used(self, name): theoretical = self._tolerance_theoretical_quantity() if not theoretical: return Decimal(0) return round( - ((self._tolerance_actual_quantity() - theoretical) + ((self._tolerance_targeted_quantity() - theoretical) / theoretical) * Decimal(100), 5) def get_tolerance_min(self, name): @@ -1566,16 +1579,13 @@ class SaleLine(metaclass=PoolMeta): return Decimal(str(sale.tol_max or 0)) return Decimal(str(self.tol_max or 0)) - 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() + if self._is_empty_quantity(getattr(self, 'targeted_qt', None)): + self.targeted_qt = theoretical + targeted = self._tolerance_targeted_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)) diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 13be345..01c7aac 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -2916,7 +2916,7 @@ class PurchaseTradeTestCase(ModuleTestCase): Decimal('10')) def test_purchase_line_tolerance_uses_physical_lots_when_present(self): - 'purchase line tolerance ignores open lot.qt when physical lots exist' + 'purchase line tolerance uses the editable targeted quantity' unit = Mock() virtual = Mock(id=10, lot_type='virtual') physical = Mock(lot_type='physic') @@ -2925,6 +2925,7 @@ class PurchaseTradeTestCase(ModuleTestCase): line.unit = unit line.quantity_theorical = Decimal('100') line.quantity = Decimal('100') + line.targeted_qt = Decimal('105') line.lots = [virtual, physical] line.inherit_tol = False line.tol_min = Decimal('5') @@ -2942,13 +2943,14 @@ class PurchaseTradeTestCase(ModuleTestCase): lot_qt_model.search.assert_not_called() def test_sale_line_tolerance_uses_open_lot_qt_without_physical_lots(self): - 'sale line tolerance uses absolute lot.qt when no physical lot exists' + 'sale line tolerance uses the editable targeted quantity' 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.targeted_qt = Decimal('95') line.lots = [virtual] line.inherit_tol = False line.tol_min = Decimal('5') @@ -2971,6 +2973,7 @@ class PurchaseTradeTestCase(ModuleTestCase): line = purchase_module.Line() line.quantity_theorical = Decimal('100') line.quantity = Decimal('100') + line.targeted_qt = Decimal('111') line.lots = [physical] line.inherit_tol = False line.tol_min = Decimal('5') @@ -2987,6 +2990,7 @@ class PurchaseTradeTestCase(ModuleTestCase): line.unit = unit line.quantity_theorical = Decimal('100') line.quantity = Decimal('100') + line.targeted_qt = Decimal('94') line.lots = [virtual] line.inherit_tol = False line.tol_min = Decimal('5') @@ -3001,6 +3005,22 @@ class PurchaseTradeTestCase(ModuleTestCase): with self.assertRaises(UserError): line.check_targeted_qt_tolerance() + def test_purchase_line_initial_targeted_qt_defaults_to_contract_quantity(self): + 'purchase line targeted quantity defaults to contractual quantity' + values = {'quantity_theorical': Decimal('100')} + + purchase_module.Line._set_initial_quantity_values(values) + + self.assertEqual(values['targeted_qt'], Decimal('100.00000')) + + def test_sale_line_initial_targeted_qt_defaults_to_contract_quantity(self): + 'sale line targeted quantity defaults to contractual quantity' + values = {'quantity_theorical': Decimal('100')} + + sale_module.SaleLine._set_initial_quantity_values(values) + + self.assertEqual(values['targeted_qt'], Decimal('100.00000')) + 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')