lot quantity logic

This commit is contained in:
2026-05-13 17:39:13 +02:00
parent 343fc8527c
commit a71ce107f0
6 changed files with 636 additions and 43 deletions

View File

@@ -1016,6 +1016,181 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(pricing_model.save.call_args_list[0].args[0][0].unfixed_qt, Decimal('10'))
self.assertEqual(pricing_model.save.call_args_list[1].args[0][0].unfixed_qt, Decimal('12'))
def test_trade_line_quantity_is_readonly(self):
'trade line quantity is a technical counter, not an editable business input'
SaleLine = Pool().get('sale.line')
PurchaseLine = Pool().get('purchase.line')
self.assertTrue(SaleLine.quantity.readonly)
self.assertTrue(PurchaseLine.quantity.readonly)
def test_purchase_line_initial_quantity_uses_theoretical_quantity(self):
'purchase line initializes technical quantity from contractual quantity'
PurchaseLine = Pool().get('purchase.line')
line = Mock(
quantity=None,
quantity_theorical=Decimal('42'),
lots=[],
)
self.assertTrue(PurchaseLine._sync_initial_quantity_from_theorical(line))
self.assertEqual(line.quantity, Decimal('42.00000'))
def test_sale_line_initial_quantity_uses_theoretical_quantity(self):
'sale line initializes technical quantity from contractual quantity'
SaleLine = Pool().get('sale.line')
line = Mock(
quantity=Decimal('0'),
quantity_theorical=Decimal('24'),
lots=[],
)
self.assertTrue(SaleLine._sync_initial_quantity_from_theorical(line))
self.assertEqual(line.quantity, Decimal('24.00000'))
def test_trade_line_initial_quantity_does_not_override_existing_counter(self):
'initial sync keeps an existing technical quantity'
PurchaseLine = Pool().get('purchase.line')
physical = Mock(lot_type='virtual')
line = Mock(
quantity=Decimal('10'),
quantity_theorical=Decimal('20'),
lots=[physical],
)
self.assertFalse(PurchaseLine._sync_initial_quantity_from_theorical(line))
self.assertEqual(line.quantity, Decimal('10'))
def test_trade_line_initial_quantity_does_not_run_with_physical_lot(self):
'initial sync never overwrites the executed physical quantity'
SaleLine = Pool().get('sale.line')
physical = Mock(lot_type='physic')
line = Mock(
quantity=Decimal('0'),
quantity_theorical=Decimal('20'),
lots=[physical],
)
self.assertFalse(SaleLine._sync_initial_quantity_from_theorical(line))
self.assertEqual(line.quantity, Decimal('0'))
def test_purchase_line_amount_uses_theoretical_quantity_until_finished(self):
'purchase line amount uses contractual quantity while line remains open'
PurchaseLine = Pool().get('purchase.line')
line = PurchaseLine()
line.type = 'line'
line.quantity = Decimal('6')
line.quantity_theorical = Decimal('10')
line.finished = False
line.unit_price = Decimal('2')
line.premium = Decimal('0')
line.enable_linked_currency = False
line.linked_currency = None
line.purchase = None
self.assertEqual(line.on_change_with_amount(), Decimal('20'))
def test_purchase_line_amount_uses_physical_counter_when_finished(self):
'purchase line amount uses executed quantity once line is finished'
PurchaseLine = Pool().get('purchase.line')
line = PurchaseLine()
line.type = 'line'
line.quantity = Decimal('6')
line.quantity_theorical = Decimal('10')
line.finished = True
line.unit_price = Decimal('2')
line.premium = Decimal('0')
line.enable_linked_currency = False
line.linked_currency = None
line.purchase = None
self.assertEqual(line.on_change_with_amount(), Decimal('12'))
def test_purchase_line_amount_uses_purchase_weight_basis_when_finished(self):
'purchase line amount uses purchase weight basis for physical lots'
PurchaseLine = Pool().get('purchase.line')
bl = Mock(id=1)
unit = Mock()
lot = Mock(
lot_type='physic',
lot_hist=[Mock(quantity_type=bl)],
)
lot.get_current_quantity_converted.return_value = Decimal('8')
line = PurchaseLine()
line.type = 'line'
line.quantity = Decimal('6')
line.quantity_theorical = Decimal('10')
line.finished = True
line.unit_price = Decimal('2')
line.premium = Decimal('0')
line.enable_linked_currency = False
line.linked_currency = None
line.purchase = Mock(wb=Mock(qt_type=bl))
line.unit = unit
line.lots = [lot]
self.assertEqual(line.on_change_with_amount(), Decimal('16'))
lot.get_current_quantity_converted.assert_called_once_with(1, unit)
def test_sale_line_amount_uses_theoretical_quantity_until_finished(self):
'sale line amount uses contractual quantity while line remains open'
SaleLine = Pool().get('sale.line')
line = SaleLine()
line.type = 'line'
line.quantity = Decimal('6')
line.quantity_theorical = Decimal('10')
line.finished = False
line.unit_price = Decimal('2')
line.premium = Decimal('0')
line.enable_linked_currency = False
line.linked_currency = None
line.sale = None
self.assertEqual(line.on_change_with_amount(), Decimal('20'))
def test_sale_line_amount_uses_physical_counter_when_finished(self):
'sale line amount uses executed quantity once line is finished'
SaleLine = Pool().get('sale.line')
line = SaleLine()
line.type = 'line'
line.quantity = Decimal('6')
line.quantity_theorical = Decimal('10')
line.finished = True
line.unit_price = Decimal('2')
line.premium = Decimal('0')
line.enable_linked_currency = False
line.linked_currency = None
line.sale = None
self.assertEqual(line.on_change_with_amount(), Decimal('12'))
def test_sale_line_amount_uses_sale_weight_basis_when_finished(self):
'sale line amount uses sale weight basis for physical lots'
SaleLine = Pool().get('sale.line')
bl = Mock(id=1)
lr = Mock(id=2)
unit = Mock()
lot = Mock(
lot_type='physic',
lot_hist=[Mock(quantity_type=bl), Mock(quantity_type=lr)],
)
lot.get_current_quantity_converted.return_value = Decimal('7')
line = SaleLine()
line.type = 'line'
line.quantity = Decimal('8')
line.quantity_theorical = Decimal('10')
line.finished = True
line.unit_price = Decimal('2')
line.premium = Decimal('0')
line.enable_linked_currency = False
line.linked_currency = None
line.sale = Mock(wb=Mock(qt_type=lr))
line.unit = unit
line.lots = [lot]
self.assertEqual(line.on_change_with_amount(), Decimal('14'))
lot.get_current_quantity_converted.assert_called_once_with(2, unit)
def test_pricing_manual_fields_are_editable_except_eod(self):
'manual pricing rows only edit quantity and settlement while derived values stay computed'
Pricing = Pool().get('pricing.pricing')