From b4d944b10c68b6b423401a94d3bd2f9e77c80169 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Thu, 14 May 2026 12:18:31 +0200 Subject: [PATCH] bug check qt --- modules/purchase_trade/purchase.py | 19 ++++++++++++--- modules/purchase_trade/sale.py | 17 +++++++++++-- modules/purchase_trade/tests/test_module.py | 27 ++++++++++++++++----- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index 04bef0f..07ba2da 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -1178,6 +1178,19 @@ class Line(metaclass=PoolMeta): values['quantity'] = Decimal(str(quantity)).quantize( Decimal("0.00001")) + @classmethod + def _set_quantity_counter_values_from_theorical(cls, records, values): + if 'quantity_theorical' not in values: + return + if any(cls._has_physical_lot(record) for record in records): + return + quantity = values.get('quantity_theorical') + if cls._is_empty_quantity(quantity): + quantity = Decimal(0) + else: + quantity = Decimal(str(quantity)).quantize(Decimal("0.00001")) + values['quantity'] = quantity + @classmethod def _fresh_lines_for_quantity_consistency(cls, lines): try: @@ -1652,14 +1665,14 @@ class Line(metaclass=PoolMeta): if 'quantity_theorical' in values: for record in records: old_values[record.id] = record.quantity_theorical - + cls._set_quantity_counter_values_from_theorical( + records, values) + super().write(*args) lines = sum(args[::2], []) for line in lines: if line.id in old_values: - if cls._sync_quantity_counter_from_theorical(line): - cls.save([line]) new = Decimal(line.quantity_theorical or 0) virtual_lots = [lot for lot in (line.lots or []) if lot.lot_type == 'virtual'] if not virtual_lots: diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 1dcb338..5217c66 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -1137,6 +1137,19 @@ class SaleLine(metaclass=PoolMeta): values['quantity'] = Decimal(str(quantity)).quantize( Decimal("0.00001")) + @classmethod + def _set_quantity_counter_values_from_theorical(cls, records, values): + if 'quantity_theorical' not in values: + return + if any(cls._has_physical_lot(record) for record in records): + return + quantity = values.get('quantity_theorical') + if cls._is_empty_quantity(quantity): + quantity = Decimal(0) + else: + quantity = Decimal(str(quantity)).quantize(Decimal("0.00001")) + values['quantity'] = quantity + @classmethod def _fresh_lines_for_quantity_consistency(cls, lines): try: @@ -1765,6 +1778,8 @@ class SaleLine(metaclass=PoolMeta): if 'quantity_theorical' in values: for record in records: old_values[record.id] = record.quantity_theorical + cls._set_quantity_counter_values_from_theorical( + records, values) super().write(*args) @@ -1772,8 +1787,6 @@ class SaleLine(metaclass=PoolMeta): for line in lines: if line.id not in old_values: continue - if cls._sync_quantity_counter_from_theorical(line): - cls.save([line]) if old_values[line.id] is None: continue old = Decimal(old_values[line.id] or 0) diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index ca8127f..9724908 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -1519,7 +1519,7 @@ class PurchaseTradeTestCase(ModuleTestCase): 'trytond.modules.purchase_trade.sale.Pool' ) as PoolMock, patch( 'trytond.modules.purchase_trade.sale.super' - ) as super_mock, patch.object(SaleLine, 'save'): + ) as super_mock, patch.object(SaleLine, 'save') as save: PoolMock.return_value.get.side_effect = lambda name: { 'lot.lot': lot_model, 'lot.qt': lotqt_model, @@ -1528,18 +1528,22 @@ class PurchaseTradeTestCase(ModuleTestCase): def fake_super_write(*args): for records, values in zip(args[::2], args[1::2]): if 'quantity_theorical' in values: + self.assertEqual(values['quantity'], Decimal('12.00000')) for record in records: record.quantity_theorical = values['quantity_theorical'] + record.quantity = values['quantity'] super_mock.return_value.write.side_effect = fake_super_write SaleLine.write([line], {'quantity_theorical': Decimal('12')}) self.assertEqual(lqt.lot_quantity, Decimal('12')) + self.assertEqual(line.quantity, Decimal('12.00000')) vlot.set_current_quantity.assert_called_once_with( Decimal('12'), Decimal('12'), 1) lot_model.save.assert_called() lotqt_model.save.assert_called() + save.assert_not_called() def test_sale_line_write_blocks_theorical_qty_decrease_when_no_open_quantity(self): 'sale line write blocks contractual qty decrease when open lot.qt is insufficient' @@ -1561,7 +1565,7 @@ class PurchaseTradeTestCase(ModuleTestCase): 'trytond.modules.purchase_trade.sale.Pool' ) as PoolMock, patch( 'trytond.modules.purchase_trade.sale.super' - ) as super_mock, patch.object(SaleLine, 'save'): + ) as super_mock, patch.object(SaleLine, 'save') as save: PoolMock.return_value.get.side_effect = lambda name: { 'lot.lot': lot_model, 'lot.qt': lotqt_model, @@ -1570,13 +1574,16 @@ class PurchaseTradeTestCase(ModuleTestCase): def fake_super_write(*args): for records, values in zip(args[::2], args[1::2]): if 'quantity_theorical' in values: + self.assertEqual(values['quantity'], Decimal('8.00000')) for record in records: record.quantity_theorical = values['quantity_theorical'] + record.quantity = values['quantity'] super_mock.return_value.write.side_effect = fake_super_write with self.assertRaises(UserError): SaleLine.write([line], {'quantity_theorical': Decimal('8')}) + save.assert_not_called() def test_purchase_line_write_syncs_open_lot_qt_with_physical_lots(self): 'purchase line write keeps open lot.qt net of existing physical lots' @@ -1600,7 +1607,7 @@ class PurchaseTradeTestCase(ModuleTestCase): 'trytond.modules.purchase_trade.purchase.Pool' ) as PoolMock, patch( 'trytond.modules.purchase_trade.purchase.super' - ) as super_mock, patch.object(PurchaseLine, 'save'): + ) as super_mock, patch.object(PurchaseLine, 'save') as save: PoolMock.return_value.get.side_effect = lambda name: { 'lot.lot': lot_model, 'lot.qt': lotqt_model, @@ -1622,6 +1629,7 @@ class PurchaseTradeTestCase(ModuleTestCase): vlot.set_current_quantity.assert_not_called() lot_model.save.assert_not_called() lotqt_model.save.assert_not_called() + save.assert_not_called() def test_purchase_line_write_syncs_quantity_without_physical_lots(self): 'purchase line write keeps quantity equal to contractual qty without physical lots' @@ -1657,8 +1665,10 @@ class PurchaseTradeTestCase(ModuleTestCase): def fake_super_write(*args): for records, values in zip(args[::2], args[1::2]): if 'quantity_theorical' in values: + self.assertEqual(values['quantity'], Decimal('2000.00000')) for record in records: record.quantity_theorical = values['quantity_theorical'] + record.quantity = values['quantity'] super_mock.return_value.write.side_effect = fake_super_write @@ -1669,7 +1679,7 @@ class PurchaseTradeTestCase(ModuleTestCase): self.assertEqual(free_lqt.lot_quantity, Decimal('550.00000')) vlot.set_current_quantity.assert_called_once_with( Decimal('2000.00000'), Decimal('2000.00000'), 1) - save.assert_called() + save.assert_not_called() def test_purchase_line_write_syncs_virtual_fee_quantity(self): 'purchase line write updates fee quantity when only virtual lot exists' @@ -1692,7 +1702,7 @@ class PurchaseTradeTestCase(ModuleTestCase): 'trytond.modules.purchase_trade.purchase.Pool' ) as PoolMock, patch( 'trytond.modules.purchase_trade.purchase.super' - ) as super_mock, patch.object(PurchaseLine, 'save'): + ) as super_mock, patch.object(PurchaseLine, 'save') as save: PoolMock.return_value.get.side_effect = lambda name: { 'lot.lot': lot_model, 'lot.qt': lotqt_model, @@ -1701,8 +1711,10 @@ class PurchaseTradeTestCase(ModuleTestCase): def fake_super_write(*args): for records, values in zip(args[::2], args[1::2]): if 'quantity_theorical' in values: + self.assertEqual(values['quantity'], Decimal('10000.00000')) for record in records: record.quantity_theorical = values['quantity_theorical'] + record.quantity = values['quantity'] super_mock.return_value.write.side_effect = fake_super_write @@ -1712,6 +1724,7 @@ class PurchaseTradeTestCase(ModuleTestCase): self.assertEqual(lqt.lot_quantity, Decimal('10000.00000')) fee.sync_quantity_from_lots.assert_called_once_with() fee.adjust_purchase_values.assert_called_once_with() + save.assert_not_called() def test_purchase_line_write_initial_theorical_qty_does_not_double_open_lot(self): 'purchase line write does not re-add quantity when initializing contractual qty' @@ -1729,7 +1742,7 @@ class PurchaseTradeTestCase(ModuleTestCase): 'trytond.modules.purchase_trade.purchase.Pool' ) as PoolMock, patch( 'trytond.modules.purchase_trade.purchase.super' - ) as super_mock: + ) as super_mock, patch.object(PurchaseLine, 'save') as save: PoolMock.return_value.get.side_effect = lambda name: { 'lot.lot': lot_model, 'lot.qt': lotqt_model, @@ -1740,6 +1753,7 @@ class PurchaseTradeTestCase(ModuleTestCase): if 'quantity_theorical' in values: for record in records: record.quantity_theorical = values['quantity_theorical'] + record.quantity = values['quantity'] super_mock.return_value.write.side_effect = fake_super_write @@ -1749,6 +1763,7 @@ class PurchaseTradeTestCase(ModuleTestCase): vlot.set_current_quantity.assert_not_called() lot_model.save.assert_not_called() lotqt_model.save.assert_not_called() + save.assert_not_called() def test_party_execution_achieved_percent_uses_real_area_statistics(self): 'party execution achieved percent reflects the controller share in its area'