Targeted Qt

This commit is contained in:
2026-05-19 11:12:52 +02:00
parent 8ff380e229
commit 22861dbc91
3 changed files with 72 additions and 32 deletions

View File

@@ -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))

View File

@@ -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))

View File

@@ -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')