diff --git a/account_commission/README.rst b/account_commission/README.rst index 05b42af32..05dee50dc 100644 --- a/account_commission/README.rst +++ b/account_commission/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - =================== Account commissions =================== @@ -17,7 +13,7 @@ Account commissions .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcommission-lightgray.png?logo=github @@ -55,6 +51,12 @@ For selecting invoice status in commissions: Note that when refunding an invoice, the corresponding reversed commission will be settled as well, resulting in a 0 net commission between both operations. +#. For payment-based commissions, you can choose how settlements are grouped. +By default, they’re grouped by 'Invoice Date', but you can also group them +by 'Payment Date'. +For example, if you select 'Payment Date', all commissions +related to payments made on the same period will be grouped together in a single settlement. + Usage ===== diff --git a/account_commission/models/commission.py b/account_commission/models/commission.py index 964bf63bb..88c07fe39 100644 --- a/account_commission/models/commission.py +++ b/account_commission/models/commission.py @@ -14,3 +14,8 @@ class Commission(models.Model): "* 'Invoice Based': Commissions are settled when the invoice is issued.\n" "* 'Payment Based': Commissions are settled when the invoice is paid (or refunded).", ) + settled_dates_based_on = fields.Selection( + [("inv", "Invoice Date"), ("payment", "Payment Date")], + default="inv", + help="Select the date to use for settling the commissions:\n", + ) diff --git a/account_commission/models/commission_settlement.py b/account_commission/models/commission_settlement.py index 9f9b94509..7697118bc 100644 --- a/account_commission/models/commission_settlement.py +++ b/account_commission/models/commission_settlement.py @@ -34,6 +34,13 @@ class CommissionSettlement(models.Model): comodel_name="account.move", compute="_compute_invoice_id", ) + commission_grouped_by = fields.Selection( + related="agent_id.commission_id.settled_dates_based_on", + string="Commission By", + help="Shows the field by which the commissions have been grouped.", + store=True, + readonly=True, + ) def _compute_can_edit(self): """Make settlements coming from invoice lines to not be editable.""" diff --git a/account_commission/readme/CONFIGURE.rst b/account_commission/readme/CONFIGURE.rst index 04e0739b6..170bbd2a1 100644 --- a/account_commission/readme/CONFIGURE.rst +++ b/account_commission/readme/CONFIGURE.rst @@ -6,3 +6,9 @@ For selecting invoice status in commissions: * **Payment Based**: Commissions are settled when the invoice is paid or refunded. Note that when refunding an invoice, the corresponding reversed commission will be settled as well, resulting in a 0 net commission between both operations. + +#. For payment-based commissions, you can choose how settlements are grouped. +By default, they’re grouped by 'Invoice Date', but you can also group them +by 'Payment Date'. +For example, if you select 'Payment Date', all commissions +related to payments made on the same period will be grouped together in a single settlement. diff --git a/account_commission/static/description/index.html b/account_commission/static/description/index.html index 978a99999..659bdd47a 100644 --- a/account_commission/static/description/index.html +++ b/account_commission/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Account commissions -
+
+

Account commissions

- - -Odoo Community Association - -
-

Account commissions

-

Beta License: AGPL-3 OCA/commission Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/commission Translate me on Weblate Try me on Runboat

This module adds the function to calculate commissions in invoices (account moves).

It also allows to create vendor bills from settlements for external agents.

This module depends on the commission module.

@@ -393,7 +388,7 @@

Account commissions

-

Configuration

+

Configuration

For selecting invoice status in commissions:

  1. Edit or create a new record to select the invoice status for settling the commissions.
+

#. For payment-based commissions, you can choose how settlements are grouped. +By default, they’re grouped by ‘Invoice Date’, but you can also group them +by ‘Payment Date’. +For example, if you select ‘Payment Date’, all commissions +related to payments made on the same period will be grouped together in a single settlement.

-

Usage

+

Usage

For adding commissions on invoices:

  1. Go to Invoicing > Customers > Invoices.
  2. @@ -448,7 +448,7 @@

    Usage

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -456,15 +456,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Tecnativa
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -509,6 +509,5 @@

Maintainers

-
diff --git a/account_commission/tests/test_account_commission.py b/account_commission/tests/test_account_commission.py index 6411dbff8..92f8c43bf 100644 --- a/account_commission/tests/test_account_commission.py +++ b/account_commission/tests/test_account_commission.py @@ -23,6 +23,24 @@ def setUpClass(cls): [("type", "=", "purchase"), ("company_id", "=", cls.company.id)], limit=1 ) cls.commission_net_paid.write({"invoice_state": "paid"}) + cls.commission_net_paid_date = cls.commission_model.create( + { + "name": "10% fixed commission (Net amount) - Invoice Based -Payment Date", + "fix_qty": 10.0, + "amount_base_type": "net_amount", + "invoice_state": "paid", + "settled_dates_based_on": "payment", + } + ) + cls.agent_monthly_payment_date = cls.res_partner_model.create( + { + "name": "Test Agent Payment - Monthly", + "agent": True, + "settlement": "monthly", + "lang": "en_US", + "commission_id": cls.commission_net_paid_date.id, + } + ) cls.commission_net_invoice = cls.commission_model.create( { "name": "10% fixed commission (Net amount) - Invoice Based", @@ -724,3 +742,30 @@ def test_payment_date_settlement(self): settlements = self.settle_model.search([("state", "=", "settled")]) self.assertEqual(1, len(settlements)) self.assertEqual(1, len(settlements.line_ids)) + + def test_groupby_payment_date_settlement(self): + """ + Two invoices paid in 2 differents agent periods, with commission + grouped by payment date it will generate one settlement with two lines + """ + date = fields.Date.today() + + invoice_after_cutoff = self._create_invoice( + self.agent_monthly_payment_date, + self.commission_net_paid, + date=date - relativedelta(months=3), + ) + invoice_after_cutoff.invoice_line_ids.agent_ids._compute_amount() + invoice = self._create_invoice( + self.agent_monthly_payment_date, + self.commission_net_paid, + date=date - relativedelta(months=2), + ) + invoice.invoice_line_ids.agent_ids._compute_amount() + (invoice_after_cutoff + invoice).action_post() + self._register_payment(invoice_after_cutoff, date) + self._register_payment(invoice, date) + self._settle_agent_invoice(self.agent_monthly_payment_date, 1) + settlements = self.settle_model.search([("state", "=", "settled")]) + self.assertEqual(1, len(settlements)) + self.assertEqual(2, len(settlements.line_ids)) diff --git a/account_commission/views/commission_settlement_views.xml b/account_commission/views/commission_settlement_views.xml index 772b25a9e..35e22df47 100644 --- a/account_commission/views/commission_settlement_views.xml +++ b/account_commission/views/commission_settlement_views.xml @@ -11,6 +11,9 @@ name="decoration-danger" >state == 'except_invoice' + + + @@ -41,6 +44,7 @@ attrs="{'invisible': [('state', '!=', 'invoiced')]}" groups="account.group_account_invoice" /> + + diff --git a/account_commission/wizards/commission_make_settle.py b/account_commission/wizards/commission_make_settle.py index a53225058..3c78ca095 100644 --- a/account_commission/wizards/commission_make_settle.py +++ b/account_commission/wizards/commission_make_settle.py @@ -32,11 +32,37 @@ def _get_agent_lines(self, agent, date_to_agent): """Filter sales invoice agent lines for this type of settlement.""" if self.settlement_type != "sale_invoice": return super()._get_agent_lines(agent, date_to_agent) - return self.env["account.invoice.line.agent"].search( - self._get_account_settle_domain(agent, date_to_agent), - order="invoice_date", + + lines = self.env["account.invoice.line.agent"].search( + self._get_account_settle_domain(agent, date_to_agent) ) + if agent.commission_id.settled_dates_based_on == "payment": + invoices = lines.mapped("invoice_id") + payment_date_by_inv = {} + + for inv in invoices: + invoice_partials, _ = inv._get_reconciled_invoices_partials() + dates = [ + cp_line.date + for _p, _amt, cp_line in invoice_partials + if cp_line.date + ] + if dates: + payment_date_by_inv[inv.id] = max(dates) + + # Filtra solo le righe con fatture che hanno pagamenti entro date_payment_to + lines = lines.filtered(lambda l: l.invoice_id.id in payment_date_by_inv) + + return lines.sorted( + key=lambda l: ( + payment_date_by_inv.get(l.invoice_id.id), + l.id, + ) + ) + + return lines.sorted(key=lambda l: (l.invoice_date, l.id)) + def _prepare_settlement_line_vals(self, settlement, line): """Prepare extra settlement values when the source is a sales invoice agent line. @@ -56,3 +82,23 @@ def _prepare_settlement_line_vals(self, settlement, line): def action_settle(self): self = self.with_context(date_payment_to=self.date_payment_to) return super().action_settle() + + def get_period_date(self, line): + if line.agent_id.commission_id.settled_dates_based_on != "payment": + return super().get_period_date(line) + return self.get_latest_payment_date(line.invoice_id) + + def get_latest_payment_date(self, invoice): + """Get the latest payment date for an invoice.""" + payments_dates = [] + ( + invoice_partials, + exchange_diff_moves, + ) = invoice._get_reconciled_invoices_partials() + for ( + _partial, + _amount, + counterpart_line, + ) in invoice_partials: + payments_dates.append(counterpart_line.date) + return max(payments_dates) if payments_dates else invoice.invoice_date diff --git a/commission/wizards/commission_make_settle.py b/commission/wizards/commission_make_settle.py index f5555cac0..8fb3f1133 100644 --- a/commission/wizards/commission_make_settle.py +++ b/commission/wizards/commission_make_settle.py @@ -115,6 +115,10 @@ def _agent_lines_groupby(self, agent_line): def _agent_lines_sorted(self, agent_line): return agent_line.company_id.id, agent_line.currency_id.id + def get_period_date(self, line): + """Get the date to use for period calculation.""" + return line.invoice_date + def action_settle(self): self.ensure_one() settlement_obj = self.env["commission.settlement"] @@ -145,8 +149,9 @@ def action_settle(self): pos += 1 if line._skip_settlement(): continue - if line.invoice_date > sett_to: - sett_from = self._get_period_start(agent, line.invoice_date) + date_to_compare = self.get_period_date(line) + if date_to_compare > sett_to: + sett_from = self._get_period_start(agent, date_to_compare) sett_to = self._get_next_period_date(agent, sett_from) sett_to -= timedelta(days=1) settlement = self._get_settlement(