Lot invoicing + Fee padding

This commit is contained in:
2026-06-23 14:31:52 +02:00
parent 50b262dbd2
commit 83b879d200
3 changed files with 88 additions and 4 deletions

View File

@@ -393,9 +393,16 @@ class Fee(ModelSQL,ModelView):
if fee.purchase:
fl = FeeLots.search([('fee','=',fee.id)])
logger.info("PROCESS_FROM_FEE:%s",fl)
fee.adjust_purchase_values()
invoices = Purchase._process_invoice(
[fee.purchase], [e.lot for e in fl], 'service')
if was_invoiced:
with Transaction().set_context(
_purchase_trade_ignore_fee_padding=True):
fee.adjust_purchase_values()
invoices = Purchase._process_invoice(
[fee.purchase], [e.lot for e in fl], 'service')
else:
fee.adjust_purchase_values()
invoices = Purchase._process_invoice(
[fee.purchase], [e.lot for e in fl], 'service')
invoice = invoices.get(fee.purchase) if invoices else None
logger.info(
"PROCESS_FROM_FEE_RESULT: fee=%s purchase=%s invoice=%s "
@@ -678,6 +685,8 @@ class Fee(ModelSQL,ModelView):
return self._convert_padding_quantity(padding, source_unit)
def get_padding_quantity(self):
if Transaction().context.get('_purchase_trade_ignore_fee_padding'):
return Decimal(0)
if not getattr(self, 'add_padding', False):
return Decimal(0)
return sum(

View File

@@ -1960,7 +1960,11 @@ class Line(metaclass=PoolMeta):
invoice_line.quantity = 1
elif fee.mode == 'ppack':
invoice_line.quantity = fee.quantity
elif fee.mode == 'perqt' and getattr(fee, 'add_padding', False):
elif (
fee.mode == 'perqt'
and getattr(fee, 'add_padding', False)
and not Transaction().context.get(
'_purchase_trade_ignore_fee_padding')):
invoice_line.quantity = fee.get_billing_quantity()
else:
invoice_line.quantity = fee.get_fee_lots_qt()

View File

@@ -3105,6 +3105,36 @@ class PurchaseTradeTestCase(ModuleTestCase):
fee, '_get_effective_fee_lots', return_value=[lot]):
self.assertEqual(fee.get_amount(), Decimal('14.00'))
def test_fee_dn_cn_amount_ignores_padding_for_current_value(self):
'fee DN/CN current value ignores padding while reversal keeps prior line'
Fee = Pool().get('fee.fee')
fee = Fee()
unit = SimpleNamespace(id=1)
sale_line = SimpleNamespace(unit=unit, unit_price=Decimal('100'))
lot = SimpleNamespace(
id=1,
lot_type='physic',
sale_line=sale_line,
sale_invoice_padding=Decimal('2'),
lot_hist=[],
get_current_quantity_converted=(
lambda state=0, unit=None: Decimal('5')))
fee.mode = 'pprice'
fee.price = Decimal('2')
fee.add_padding = True
fee.unit = unit
fee.quantity = Decimal('5')
fee.line = None
fee.sale_line = sale_line
fee.shipment_in = None
with patch.object(
fee, '_get_effective_fee_lots', return_value=[lot]):
self.assertEqual(fee.get_amount(), Decimal('14.00'))
with Transaction().set_context(
_purchase_trade_ignore_fee_padding=True):
self.assertEqual(fee.get_amount(), Decimal('10.00'))
def test_percent_price_fee_warns_when_line_lots_are_partial(self):
'% price fee warns when it does not cover all line lots'
Fee = Pool().get('fee.fee')
@@ -3238,6 +3268,47 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(invoice_line.quantity, Decimal('7'))
fee.get_billing_quantity.assert_called_once_with()
def test_purchase_trade_service_fee_dn_cn_ignores_padding_quantity(self):
'fee DN/CN current invoice line uses final lot quantity without padding'
Line = purchase_module.Line
line = Line()
line.purchase = Mock(id=20)
lot = Mock(id=30)
invoice_line = Mock(quantity=Decimal('7'), unit_price=Decimal('10'))
fee = Mock(
state='invoiced',
mode='perqt',
add_padding=True,
warn_percent_price_partial_lots=Mock(),
get_billing_quantity=Mock(return_value=Decimal('7')),
get_fee_lots_qt=Mock(return_value=Decimal('5')))
fee_model = Mock()
fee_model.search.return_value = [fee]
with patch(
'trytond.modules.purchase_trade.purchase.Pool'
) as PoolMock, patch.object(
Line, '_get_last_fee_invoice_line',
return_value=Mock(
quantity=Decimal('7'),
unit_price=Decimal('10'),
invoice=Mock(party=Mock()))), patch.object(
Line, '_get_fee_reversal_invoice_line',
return_value=Mock()):
PoolMock.return_value.get.side_effect = lambda name: {
'fee.fee': fee_model,
}[name]
with Transaction().set_context(
_purchase_trade_ignore_fee_padding=True):
result = line._get_service_fee_invoice_lines(
invoice_line, lot)
self.assertEqual(result, [ANY, invoice_line])
self.assertEqual(invoice_line.quantity, Decimal('5'))
fee.get_billing_quantity.assert_not_called()
fee.get_fee_lots_qt.assert_called_once_with()
def test_lot_invoice_sale_padding_refreshes_per_quantity_fee_amount(self):
'sale padding refreshes displayed per quantity fee amount'
start = lot_module.LotInvoiceStart()