From 8441bf98e7cf1ffb1bd44268e26d6b9644c3cbe0 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Wed, 17 Jun 2026 11:25:33 +0200 Subject: [PATCH] Fee report --- modules/purchase_trade/fee.py | 80 +++++++++++++------ modules/purchase_trade/fee.xml | 9 +++ modules/purchase_trade/tests/test_module.py | 17 ++++ .../view/fee_report_context_form.xml | 2 + .../purchase_trade/view/fee_report_list.xml | 4 +- 5 files changed, 85 insertions(+), 27 deletions(-) diff --git a/modules/purchase_trade/fee.py b/modules/purchase_trade/fee.py index 7536eb9..c2a7502 100755 --- a/modules/purchase_trade/fee.py +++ b/modules/purchase_trade/fee.py @@ -9,7 +9,7 @@ from trytond.transaction import Transaction, inactive_records from decimal import getcontext, Decimal, ROUND_UP, ROUND_HALF_UP from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr from sql.conditionals import Case -from sql import Column, Literal +from sql import Column, Literal, Null from sql.functions import CurrentTimestamp, DateTrunc from trytond.wizard import Button, StateTransition, StateView, Wizard from itertools import chain, groupby @@ -1513,8 +1513,8 @@ class FeeLots(ModelSQL,ModelView): Fee._regenerate_fee_pnl( fees=Fee.browse(list(fee_ids)), lots=lots) -class FeeReport( - ModelSQL, ModelView): +class FeeReport( + ModelSQL, ModelView): "Fee Report" __name__ = 'fee.report' r_purchase_line = fields.Many2One('purchase.line', "Purchase line") @@ -1543,12 +1543,14 @@ class FeeReport( r_fee_quantity = fields.Function(fields.Numeric("Qt",digits=(1,4)),'get_quantity') r_fee_unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit') r_purchase = fields.Many2One('purchase.purchase',"Purchase", ondelete='CASCADE') - r_fee_amount = fields.Function(fields.Numeric("Amount", digits=(1,4)),'get_amount') - r_inv = fields.Function(fields.Many2One('account.invoice',"Invoice"),'get_invoice') - r_state = fields.Selection([ - ('not invoiced', 'Not invoiced'), - ('invoiced', 'Invoiced'), - ], string='State', readonly=True) + r_fee_amount = fields.Function(fields.Numeric("Amount", digits=(1,4)),'get_amount') + r_inv = fields.Function(fields.Many2One('account.invoice',"Invoice"),'get_invoice') + r_dn_cn = fields.Function(fields.Many2One('account.invoice',"DN/CN"), + 'get_dn_cn') + r_state = fields.Selection([ + ('not invoiced', 'Not invoiced'), + ('invoiced', 'Invoiced'), + ], string='State', readonly=True) #r_fee_lots = fields.Function(fields.Many2Many('lot.lot', None, None, "Lots"),'get_lots')#, searcher='search_lots') #r_lots = fields.Many2Many('fee.lots', 'fee', 'lot',"Lots",domain=[('id', 'in', Eval('r_fee_lots',[]))] ) @@ -1566,10 +1568,17 @@ class FeeReport( "get_shipment_origin", ) - def get_invoice(self,name): - if self.r_purchase: - if self.r_purchase.invoices: - return self.r_purchase.invoices[0] + def get_invoice(self,name): + Fee = Pool().get('fee.fee') + fee = Fee(self.id) + invoice = fee.get_invoice(name) + return getattr(invoice, 'id', invoice) + + def get_dn_cn(self, name): + Fee = Pool().get('fee.fee') + fee = Fee(self.id) + dn_cn = getattr(fee, 'dn_cn', None) + return getattr(dn_cn, 'id', dn_cn) def get_shipment_origin(self, name): if self.r_shipment_in: @@ -1595,10 +1604,16 @@ class FeeReport( fee = Fee(self.id) return fee.get_quantity() - def get_amount(self,name=None): - Fee = Pool().get('fee.fee') - fee = Fee(self.id) - return fee.get_amount() + def get_amount(self,name=None): + Fee = Pool().get('fee.fee') + fee = Fee(self.id) + return fee.get_amount() + + @classmethod + @ModelView.button + def invoice(cls, reports): + Fee = Pool().get('fee.fee') + Fee.invoice(Fee.browse([report.id for report in reports])) @classmethod def table_query(cls): @@ -1620,10 +1635,11 @@ class FeeReport( sale = context.get('sale') shipment_in = context.get('shipment_in') shipment_out = context.get('shipment_out') - shipment_internal = context.get('shipment_internal') - asof = context.get('asof') - todate = context.get('todate') - wh = ((fr.create_date >= asof) & ((fr.create_date-datetime.timedelta(1)) <= todate)) + shipment_internal = context.get('shipment_internal') + invoice_status = context.get('invoice_status') + asof = context.get('asof') + todate = context.get('todate') + wh = ((fr.create_date >= asof) & ((fr.create_date-datetime.timedelta(1)) <= todate)) if party: wh &= (fr.fee_counterparty == party) if fee_type: @@ -1632,8 +1648,14 @@ class FeeReport( wh &= (pu.id == purchase) if sale: wh &= (sa.id == sale) - if shipment_in: - wh &= (fr.shipment_in == shipment_in) + if shipment_in: + wh &= (fr.shipment_in == shipment_in) + if invoice_status == 'not_invoiced': + wh &= (fr.state == 'not invoiced') + elif invoice_status == 'invoiced_without_dn_cn': + wh &= ((fr.state == 'invoiced') & (fr.dn_cn == Null)) + elif invoice_status == 'invoiced_with_dn_cn': + wh &= ((fr.state == 'invoiced') & (fr.dn_cn != Null)) # if shipment_out: # wh &= (fr.shipment_out == shipment_out) @@ -1679,15 +1701,21 @@ class FeeReport( ('r_fee_counterparty', operator, operand, *extra), ] -class FeeContext(ModelView): +class FeeContext(ModelView): "Fee Context" __name__ = 'fee.context' asof = fields.Date("As of") todate = fields.Date("To") party = fields.Many2One('party.party', "Counterparty") - fee_type = fields.Many2One('product.product', 'Fee type') - purchase = fields.Many2One('purchase.purchase', "Purchase") + fee_type = fields.Many2One('product.product', 'Fee type') + invoice_status = fields.Selection([ + (None, ''), + ('not_invoiced', 'Not invoiced'), + ('invoiced_without_dn_cn', 'Invoiced without DN/CN'), + ('invoiced_with_dn_cn', 'Invoiced with DN/CN'), + ], "Invoice status") + purchase = fields.Many2One('purchase.purchase', "Purchase") sale = fields.Many2One('sale.sale', "Sale") shipment_in = fields.Many2One('stock.shipment.in',"Shipment In") shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out") diff --git a/modules/purchase_trade/fee.xml b/modules/purchase_trade/fee.xml index 29107e2..67b6f6c 100755 --- a/modules/purchase_trade/fee.xml +++ b/modules/purchase_trade/fee.xml @@ -80,6 +80,15 @@ this repository contains the full copyright notices and license terms. --> + + fee.report + invoice + Invoice + + + + + purchase.line apply_default_fees diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 1f26778..2b7a281 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -5537,6 +5537,23 @@ class PurchaseTradeTestCase(ModuleTestCase): self.assertEqual(Fee._get_generated_purchases_to_delete([fee]), []) + def test_fee_report_invoice_delegates_to_fee_invoice(self): + 'fee report invoice button invoices the underlying fees' + FeeReport = Pool().get('fee.report') + report = FeeReport() + report.id = 10 + fee_model = Mock() + fees = [Mock(id=10)] + fee_model.browse.return_value = fees + + with patch('trytond.modules.purchase_trade.fee.Pool') as PoolMock: + PoolMock.return_value.get.return_value = fee_model + + FeeReport.invoice([report]) + + fee_model.browse.assert_called_once_with([10]) + fee_model.invoice.assert_called_once_with(fees) + def test_sale_report_converts_mixed_units_for_total_and_words(self): 'sale report totals prefer the virtual lot unit as common unit' Sale = Pool().get('sale.sale') diff --git a/modules/purchase_trade/view/fee_report_context_form.xml b/modules/purchase_trade/view/fee_report_context_form.xml index e963667..32c3745 100755 --- a/modules/purchase_trade/view/fee_report_context_form.xml +++ b/modules/purchase_trade/view/fee_report_context_form.xml @@ -7,6 +7,8 @@