Invoice Melya template

This commit is contained in:
2026-06-26 14:28:26 +02:00
parent c9dceb9c06
commit b7b54c8b7b
2 changed files with 82 additions and 32 deletions

View File

@@ -177,7 +177,7 @@ class Invoice(metaclass=PoolMeta):
Snapshot = Pool().get('account.invoice.line.lot.weight')
Lot = Pool().get('lot.lot')
for line in self._get_report_invoice_lines():
if (getattr(line, 'description', None) != 'Pro forma'
if (getattr(line, 'description', None) not in {'Pro forma', 'Final'}
or Decimal(str(getattr(line, 'quantity', 0) or 0)) <= 0
or not getattr(line, 'lot', None)):
continue
@@ -961,6 +961,49 @@ class Invoice(metaclass=PoolMeta):
def report_purchase_commission_total_display(self):
return self._get_report_commission_total_display_for('purchase')
@staticmethod
def _same_report_invoice_line(left, right):
if not left or not right:
return False
left_id = getattr(left, 'id', None)
right_id = getattr(right, 'id', None)
if left_id is not None and right_id is not None:
return left_id == right_id
return left is right
def _get_report_invoice_line_lots(self, invoice_line):
lots = []
seen = set()
def add_lot(lot):
if not lot or getattr(lot, 'lot_type', None) == 'virtual':
return
lot_id = getattr(lot, 'id', None)
key = lot_id if lot_id is not None else id(lot)
if key in seen:
return
seen.add(key)
lots.append(lot)
add_lot(getattr(invoice_line, 'lot', None))
trade = self._get_report_trade()
trade_lines = getattr(trade, 'lines', []) if trade else []
for line in trade_lines or []:
for lot in getattr(line, 'lots', []) or []:
if getattr(lot, 'lot_type', None) != 'physic':
continue
refs = [
getattr(lot, 'sale_invoice_line', None),
getattr(lot, 'sale_invoice_line_prov', None),
getattr(lot, 'invoice_line', None),
getattr(lot, 'invoice_line_prov', None),
]
if any(
self._same_report_invoice_line(ref, invoice_line)
for ref in refs):
add_lot(lot)
return lots
@staticmethod
def _get_report_related_lots(line):
lots = []
@@ -1044,7 +1087,7 @@ class Invoice(metaclass=PoolMeta):
net_total += padding
gross_total += padding
return net_total * sign, gross_total * sign
lots = self._get_report_preferred_lots(line)
lots = self._get_report_invoice_line_lots(line)
if lots and self._report_invoice_line_reuses_lot(line):
quantity = self._get_report_invoice_line_quantity_from_line(line)
return quantity, quantity
@@ -1093,7 +1136,11 @@ class Invoice(metaclass=PoolMeta):
@staticmethod
def _get_report_line_lot_keys(line):
keys = []
for lot in Invoice._get_report_preferred_lots(line):
invoice = getattr(line, 'invoice', None)
lots = (
invoice._get_report_invoice_line_lots(line)
if invoice else Invoice._get_report_preferred_lots(line))
for lot in lots:
lot_id = getattr(lot, 'id', None)
keys.append(lot_id if lot_id is not None else id(lot))
return tuple(sorted(keys))
@@ -1112,7 +1159,7 @@ class Invoice(metaclass=PoolMeta):
def _get_report_reused_lot_lines(self):
groups = {}
for line in self._get_report_invoice_lines():
lots = self._get_report_preferred_lots(line)
lots = self._get_report_invoice_line_lots(line)
if not lots:
continue
for lot in lots:
@@ -1237,7 +1284,10 @@ class Invoice(metaclass=PoolMeta):
snapshots = Invoice._get_report_invoice_line_weight_snapshots(line)
if snapshots and getattr(snapshots[0], 'unit', None):
return snapshots[0].unit
lots = Invoice._get_report_preferred_lots(line)
invoice = getattr(line, 'invoice', None)
lots = (
invoice._get_report_invoice_line_lots(line)
if invoice else Invoice._get_report_preferred_lots(line))
if lots and getattr(lots[0], 'lot_unit_line', None):
return lots[0].lot_unit_line
return getattr(line, 'unit', None)
@@ -1290,13 +1340,13 @@ class Invoice(metaclass=PoolMeta):
return text.upper() if text else ''
def _get_report_purchase(self):
purchases = list(self.purchases or [])
purchases = list(getattr(self, 'purchases', None) or [])
return purchases[0] if purchases else None
def _get_report_sale(self):
# Bridge invoice templates to the originating sale so FODT files can
# reuse stable sale.report_* properties instead of complex expressions.
sales = list(self.sales or [])
sales = list(getattr(self, 'sales', None) or [])
return sales[0] if sales else None
def _get_report_trade(self):
@@ -1347,7 +1397,7 @@ class Invoice(metaclass=PoolMeta):
shipments = []
seen = set()
for line in self._get_report_invoice_lines():
for lot in self._get_report_preferred_lots(line):
for lot in self._get_report_invoice_line_lots(line):
shipment = self._get_report_lot_shipment(lot)
if not shipment:
continue
@@ -1367,15 +1417,6 @@ class Invoice(metaclass=PoolMeta):
if not invoice_lines:
return []
def _same_invoice_line(left, right):
if not left or not right:
return False
left_id = getattr(left, 'id', None)
right_id = getattr(right, 'id', None)
if left_id is not None and right_id is not None:
return left_id == right_id
return left is right
trade = self._get_report_trade()
trade_lines = getattr(trade, 'lines', []) if trade else []
lots = []
@@ -1390,7 +1431,7 @@ class Invoice(metaclass=PoolMeta):
getattr(lot, 'invoice_line_prov', None),
]
if any(
_same_invoice_line(ref, invoice_line)
self._same_report_invoice_line(ref, invoice_line)
for ref in refs for invoice_line in invoice_lines):
lots.append(lot)
return lots

View File

@@ -5,7 +5,7 @@ import datetime
from contextlib import nullcontext
from decimal import Decimal
from types import SimpleNamespace
from unittest.mock import ANY, Mock, patch
from unittest.mock import ANY, Mock, call, patch
from xml.etree import ElementTree
from trytond.pool import Pool
@@ -8257,8 +8257,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
invoice.report_quantity_lines,
'910.00 LBS (910.00 LBS)')
def test_invoice_validation_creates_weight_snapshot_for_proforma_lines(self):
'invoice validation snapshots lot weights for pro forma invoice lines'
def test_invoice_validation_creates_weight_snapshot_for_report_lines(self):
'invoice validation snapshots lot weights for pro forma and final lines'
Invoice = Pool().get('account.invoice')
lot_ref = Mock(id=10)
@@ -8293,8 +8293,9 @@ class PurchaseTradeTestCase(ModuleTestCase):
invoice._create_lot_weight_snapshots()
snapshot_model.create_for_invoice_line.assert_called_once_with(
proforma, lot)
self.assertEqual(
snapshot_model.create_for_invoice_line.call_args_list,
[call(proforma, lot), call(final, lot)])
def test_invoice_report_lbs_converts_kilogram_to_lbs(self):
'invoice lbs helper converts kilogram quantities with the proper uom ratio'
@@ -8429,8 +8430,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
'999990.0 KILOGRAM (2204597.95 LBS)',
])
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'
def test_invoice_report_weights_ignore_origin_virtual_lot(self):
'invoice report falls back to invoice quantity instead of origin virtual lot'
Invoice = Pool().get('account.invoice')
virtual = Mock(id=1, lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
@@ -8448,13 +8449,14 @@ class PurchaseTradeTestCase(ModuleTestCase):
)
invoice = Invoice()
invoice.lines = [line]
line.invoice = invoice
self.assertEqual(invoice.report_net, Decimal('930'))
self.assertEqual(invoice.report_gross, Decimal('930'))
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
self.assertEqual(invoice.report_net, Decimal('1000'))
self.assertEqual(invoice.report_gross, Decimal('1000'))
self.assertEqual(invoice.report_weight_unit_upper, 'MT')
def test_invoice_report_weights_prefer_physical_lots_over_virtual(self):
'invoice uses physical lot hist values whenever physical lots exist'
def test_invoice_report_weights_use_lot_linked_to_invoice_line(self):
'invoice uses the physical lot explicitly linked to the invoice line'
Invoice = Pool().get('account.invoice')
virtual = Mock(id=1, lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
@@ -8467,16 +8469,23 @@ class PurchaseTradeTestCase(ModuleTestCase):
Decimal('950'),
Decimal('980'),
)
origin = Mock(lots=[virtual, physical])
physical.sale_invoice_line = None
line = Mock(
id=100,
type='line',
quantity=Decimal('1000'),
lot=virtual,
origin=origin,
unit=Mock(rec_name='MT'),
)
physical.sale_invoice_line_prov = line
physical.invoice_line = None
physical.invoice_line_prov = None
origin = Mock(lots=[virtual, physical])
line.origin = origin
invoice = Invoice()
invoice.lines = [line]
invoice.sales = [Mock(lines=[origin])]
line.invoice = invoice
self.assertEqual(invoice.report_net, Decimal('950'))
self.assertEqual(invoice.report_gross, Decimal('980'))