This commit is contained in:
2026-04-09 19:46:08 +02:00
parent 5ae8af84fb
commit a1ab7dec82
11 changed files with 669 additions and 118 deletions

View File

@@ -184,6 +184,20 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(
PurchaseLine.default_pricing_rule(), 'Default pricing rule')
def test_sale_and_purchase_trader_operator_domains_use_explicit_categories(self):
'sale and purchase trader/operator fields are filtered by TRADER/OPERATOR categories'
Sale = Pool().get('sale.sale')
Purchase = Pool().get('purchase.purchase')
self.assertEqual(
Sale.trader.domain, [('categories.name', '=', 'TRADER')])
self.assertEqual(
Sale.operator.domain, [('categories.name', '=', 'OPERATOR')])
self.assertEqual(
Purchase.trader.domain, [('categories.name', '=', 'TRADER')])
self.assertEqual(
Purchase.operator.domain, [('categories.name', '=', 'OPERATOR')])
def test_sale_line_write_updates_virtual_lot_when_theorical_qty_increases(self):
'sale line write increases virtual lot and open lot.qt when contractual qty grows'
SaleLine = Pool().get('sale.line')
@@ -841,15 +855,86 @@ class PurchaseTradeTestCase(ModuleTestCase):
'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MARCH 2026',
)])
def test_invoice_report_note_title_uses_total_amount_sign(self):
'final invoice title switches between credit and debit note'
def test_sale_report_uses_single_virtual_lot_hist_when_no_physical(self):
'sale report uses the unique virtual lot hist when no physical lot exists'
Sale = Pool().get('sale.sale')
virtual = Mock(lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('0'),
)
line = Mock(type='line', quantity=Decimal('1000'))
line.lots = [virtual]
line.unit = Mock(rec_name='MT')
line.del_period = Mock(description='MARCH 2026')
sale = Sale()
sale.lines = [line]
self.assertEqual(sale.report_net, Decimal('930'))
self.assertEqual(sale.report_gross, Decimal('930'))
self.assertEqual(sale.report_total_quantity, '930.0')
self.assertEqual(sale.report_quantity_unit_upper, 'LBS')
self.assertEqual(
sale.report_quantity_lines,
'930.0 LBS (NINE HUNDRED AND THIRTY POUNDS) - MARCH 2026')
def test_sale_report_prefers_physical_lot_hist_over_virtual(self):
'sale report prioritizes physical lot hist values over virtual ones'
Sale = Pool().get('sale.sale')
virtual = Mock(lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('940'),
)
physical = Mock(lot_type='physic', lot_unit_line=Mock(rec_name='LBS'))
physical.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
line = Mock(type='line', quantity=Decimal('1000'))
line.lots = [virtual, physical]
line.unit = Mock(rec_name='MT')
line.del_period = Mock(description='MARCH 2026')
sale = Sale()
sale.lines = [line]
self.assertEqual(sale.report_net, Decimal('950'))
self.assertEqual(sale.report_gross, Decimal('980'))
self.assertEqual(sale.report_total_quantity, '950.0')
self.assertEqual(sale.report_quantity_unit_upper, 'LBS')
self.assertEqual(
sale.report_quantity_lines,
'950.0 LBS (NINE HUNDRED AND FIFTY POUNDS) - MARCH 2026')
def test_invoice_report_note_title_uses_sale_direction(self):
'sale final note title is inverted from the raw amount sign'
Invoice = Pool().get('account.invoice')
debit = Invoice()
debit.type = 'out'
debit.total_amount = Decimal('10')
self.assertEqual(debit.report_note_title, 'Debit Note')
credit = Invoice()
credit.type = 'out'
credit.total_amount = Decimal('-10')
self.assertEqual(credit.report_note_title, 'Credit Note')
def test_invoice_report_note_title_keeps_inverse_for_purchase(self):
'purchase final note title keeps the opposite mapping'
Invoice = Pool().get('account.invoice')
credit = Invoice()
credit.type = 'in'
credit.total_amount = Decimal('10')
self.assertEqual(credit.report_note_title, 'Credit Note')
debit = Invoice()
debit.type = 'in'
debit.total_amount = Decimal('-10')
self.assertEqual(debit.report_note_title, 'Debit Note')
@@ -864,6 +949,187 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(invoice.report_net, Decimal('800'))
def test_invoice_report_weights_use_current_lot_hist_values(self):
'invoice net and gross weights come from the current lot hist entry'
Invoice = Pool().get('account.invoice')
unit = Mock(rec_name='LBS')
lot = Mock(lot_unit_line=unit)
lot.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
line = Mock(type='line', quantity=Decimal('1000'), lot=lot, unit=Mock(rec_name='MT'))
invoice = Invoice()
invoice.lines = [line]
self.assertEqual(invoice.report_net, Decimal('950'))
self.assertEqual(invoice.report_gross, Decimal('980'))
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
self.assertEqual(
invoice.report_quantity_lines,
'950.0 LBS (2094389.00 LBS)')
def test_invoice_report_weights_keep_line_sign_with_lot_hist_values(self):
'invoice lot hist values keep the invoice line sign for final notes'
Invoice = Pool().get('account.invoice')
positive_lot = Mock(lot_unit_line=Mock(rec_name='LBS'))
positive_lot.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
negative_lot = Mock(lot_unit_line=Mock(rec_name='LBS'))
negative_lot.get_hist_quantity.return_value = (
Decimal('150'),
Decimal('160'),
)
positive = Mock(type='line', quantity=Decimal('1000'), lot=positive_lot)
negative = Mock(type='line', quantity=Decimal('-200'), lot=negative_lot)
invoice = Invoice()
invoice.lines = [positive, negative]
self.assertEqual(invoice.report_net, Decimal('800'))
self.assertEqual(invoice.report_gross, Decimal('820'))
def test_invoice_report_weights_use_single_virtual_lot_when_no_physical(self):
'invoice uses the unique virtual lot hist when no physical lot exists'
Invoice = Pool().get('account.invoice')
virtual = Mock(id=1, lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('0'),
)
origin = Mock(lots=[virtual])
line = Mock(
type='line',
quantity=Decimal('1000'),
lot=None,
origin=origin,
unit=Mock(rec_name='MT'),
)
invoice = Invoice()
invoice.lines = [line]
self.assertEqual(invoice.report_net, Decimal('930'))
self.assertEqual(invoice.report_gross, Decimal('930'))
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
def test_invoice_report_weights_prefer_physical_lots_over_virtual(self):
'invoice uses physical lot hist values whenever physical lots exist'
Invoice = Pool().get('account.invoice')
virtual = Mock(id=1, lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('940'),
)
physical = Mock(id=2, lot_type='physic', lot_unit_line=Mock(rec_name='LBS'))
physical.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
origin = Mock(lots=[virtual, physical])
line = Mock(
type='line',
quantity=Decimal('1000'),
lot=virtual,
origin=origin,
unit=Mock(rec_name='MT'),
)
invoice = Invoice()
invoice.lines = [line]
self.assertEqual(invoice.report_net, Decimal('950'))
self.assertEqual(invoice.report_gross, Decimal('980'))
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
def test_invoice_report_shipment_uses_invoice_line_lot_not_first_trade_line(self):
'invoice shipment info comes from the lots linked to the invoiced line'
Invoice = Pool().get('account.invoice')
shipment_a = Mock(
id=1,
bl_date='2026-04-01',
bl_number='BL-A',
vessel=Mock(vessel_name='VESSEL A'),
from_location=Mock(rec_name='LOADING A'),
to_location=Mock(rec_name='DISCHARGE A'),
controller=Mock(rec_name='CTRL A'),
number='SI-A',
)
shipment_b = Mock(
id=2,
bl_date='2026-04-05',
bl_number='BL-B',
vessel=Mock(vessel_name='VESSEL B'),
from_location=Mock(rec_name='LOADING B'),
to_location=Mock(rec_name='DISCHARGE B'),
controller=Mock(rec_name='CTRL B'),
number='SI-B',
)
lot_a = Mock(id=10, lot_type='physic', lot_shipment_in=shipment_a)
lot_b = Mock(id=20, lot_type='physic', lot_shipment_in=shipment_b)
line_a = Mock(lots=[lot_a])
line_b = Mock(lots=[lot_b])
purchase = Mock(lines=[line_a, line_b])
invoice_line = Mock(type='line', lot=lot_b, origin=line_b)
invoice = Invoice()
invoice.purchases = [purchase]
invoice.lines = [invoice_line]
self.assertEqual(invoice.report_bl_nb, 'BL-B')
self.assertEqual(invoice.report_bl_date, '2026-04-05')
self.assertEqual(invoice.report_vessel, 'VESSEL B')
self.assertEqual(invoice.report_loading_port, 'LOADING B')
self.assertEqual(invoice.report_discharge_port, 'DISCHARGE B')
self.assertEqual(invoice.report_controller_name, 'CTRL B')
self.assertEqual(invoice.report_si_number, 'SI-B')
self.assertEqual(invoice.report_si_reference, 'REF-B')
def test_invoice_report_shipment_is_blank_if_invoice_mixes_shipments(self):
'invoice shipment fields stay empty when multiple shipments are invoiced together'
Invoice = Pool().get('account.invoice')
shipment_a = Mock(
id=1,
bl_date='2026-04-01',
bl_number='BL-A',
vessel=Mock(vessel_name='VESSEL A'),
from_location=Mock(rec_name='LOADING A'),
to_location=Mock(rec_name='DISCHARGE A'),
controller=Mock(rec_name='CTRL A'),
number='SI-A',
)
shipment_b = Mock(
id=2,
bl_date='2026-04-05',
bl_number='BL-B',
reference='REF-B',
vessel=Mock(vessel_name='VESSEL B'),
from_location=Mock(rec_name='LOADING B'),
to_location=Mock(rec_name='DISCHARGE B'),
controller=Mock(rec_name='CTRL B'),
number='SI-B',
)
lot_a = Mock(id=10, lot_type='physic', lot_shipment_in=shipment_a)
lot_b = Mock(id=20, lot_type='physic', lot_shipment_in=shipment_b)
line_a = Mock(type='line', lot=lot_a, origin=Mock(lots=[lot_a]))
line_b = Mock(type='line', lot=lot_b, origin=Mock(lots=[lot_b]))
invoice = Invoice()
invoice.lines = [line_a, line_b]
self.assertIsNone(invoice.report_bl_nb)
self.assertIsNone(invoice.report_bl_date)
self.assertEqual(invoice.report_vessel, None)
self.assertEqual(invoice.report_loading_port, '')
self.assertEqual(invoice.report_discharge_port, '')
self.assertEqual(invoice.report_controller_name, '')
self.assertEqual(invoice.report_si_number, '')
self.assertEqual(invoice.report_si_reference, '')
def test_invoice_report_nb_bale_sums_signed_line_lot_quantities(self):
'invoice reports packaging from the signed sum of line lot_qt values'
Invoice = Pool().get('account.invoice')
@@ -876,6 +1142,18 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(invoice.report_nb_bale, 'NB BALES: 0')
def test_invoice_report_cndn_nb_bale_displays_unchanged_for_zero(self):
'CN/DN bale label displays Unchanged when the signed balance is zero'
Invoice = Pool().get('account.invoice')
lot = Mock(lot_qt=Decimal('350'), lot_unit=Mock(symbol='bale'))
negative = Mock(type='line', quantity=Decimal('-1000'), lot=lot)
positive = Mock(type='line', quantity=Decimal('1000'), lot=lot)
invoice = Invoice()
invoice.lines = [negative, positive]
self.assertEqual(invoice.report_cndn_nb_bale, 'Unchanged')
def test_invoice_report_positive_rate_lines_keep_positive_components(self):
'invoice final note pricing section keeps only positive component lines'
Invoice = Pool().get('account.invoice')