From 2b45e7cddd68cd717b6daead396793228222e9b6 Mon Sep 17 00:00:00 2001 From: Ricardo Jara Date: Mon, 27 Jul 2026 08:59:10 -0500 Subject: [PATCH] [IMP] account_payment_line: restrict counterpart lines to payment partner By default the counterpart lines of a payment accept any partner, which allows applying invoices of a partner different from the one registered in the payment header. Add an optional company setting, disabled by default --- account_payment_line/README.rst | 22 +++++-- account_payment_line/__manifest__.py | 3 +- account_payment_line/models/__init__.py | 2 + .../models/account_payment.py | 56 ++++++++++++++++- account_payment_line/models/res_company.py | 13 ++++ .../models/res_config_settings.py | 10 +++ account_payment_line/readme/CONFIGURE.md | 11 ++++ account_payment_line/readme/CONTRIBUTORS.md | 1 + .../static/description/index.html | 52 ++++++++------- .../tests/test_account_payment_line.py | 63 +++++++++++++++++++ .../views/account_payment_views.xml | 2 + .../views/res_config_settings_views.xml | 29 +++++++++ 12 files changed, 235 insertions(+), 29 deletions(-) create mode 100644 account_payment_line/models/res_company.py create mode 100644 account_payment_line/models/res_config_settings.py create mode 100644 account_payment_line/readme/CONFIGURE.md create mode 100644 account_payment_line/views/res_config_settings_views.xml diff --git a/account_payment_line/README.rst b/account_payment_line/README.rst index 7b5bfcf24d5..e358fad1387 100644 --- a/account_payment_line/README.rst +++ b/account_payment_line/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 - ========================= Payment Counterpart Lines ========================= @@ -17,7 +13,7 @@ Payment Counterpart Lines .. |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%2Faccount--payment-lightgray.png?logo=github @@ -43,6 +39,21 @@ Add tool to proposal of payment distributions, ordering by due date .. contents:: :local: +Configuration +============= + +By default, counterpart lines let you pick any partner, so you can move +balances between partners. + +If you want to forbid that and make sure a payment only applies invoices +of the partner set on its header, go to **Invoicing / Settings** and +enable **Restrict counterpart lines to payment partner**. + +The setting is company dependent and disabled by default, so it does not +change the current behavior unless you activate it. Once enabled, the +partner of the counterpart lines is limited to the commercial entity of +the payment partner, both in the interface and on save. + Usage ===== @@ -76,6 +87,7 @@ Contributors - Christopher Ormaza. - Do Anh Duy +- Ricardo Jara (Spearhead) Maintainers ----------- diff --git a/account_payment_line/__manifest__.py b/account_payment_line/__manifest__.py index def65c9e727..708db9937d9 100644 --- a/account_payment_line/__manifest__.py +++ b/account_payment_line/__manifest__.py @@ -7,11 +7,12 @@ "author": "ForgeFlow S.L., Odoo Community Association (OCA)", "website": "https://github.com/OCA/account-payment", "category": "Account", - "version": "18.0.1.0.2", + "version": "18.0.1.1.0", "license": "AGPL-3", "depends": ["account_payment"], "data": [ "security/ir.model.access.csv", + "views/res_config_settings_views.xml", "views/account_payment_views.xml", ], "maintainers": ["ChrisOForgeFlow"], diff --git a/account_payment_line/models/__init__.py b/account_payment_line/models/__init__.py index a41b4214589..47907c8e8e5 100644 --- a/account_payment_line/models/__init__.py +++ b/account_payment_line/models/__init__.py @@ -1,3 +1,5 @@ +from . import res_company +from . import res_config_settings from . import counterpart_line from . import account_payment from . import account_move diff --git a/account_payment_line/models/account_payment.py b/account_payment_line/models/account_payment.py index a2007ccfd2a..074e5174d6f 100644 --- a/account_payment_line/models/account_payment.py +++ b/account_payment_line/models/account_payment.py @@ -1,7 +1,7 @@ # Copyright 2022 ForgeFlow, S.L. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import Command, fields, models +from odoo import Command, api, fields, models from odoo.exceptions import ValidationError @@ -20,6 +20,16 @@ class AccountPayment(models.Model): string="Write-off Account", domain="[('deprecated', '=', False), ('company_ids', '=', company_id)]", ) + restrict_counterpart_partner = fields.Boolean( + compute="_compute_restrict_counterpart_partner", + ) + + @api.depends("company_id.payment_line_restrict_counterpart_partner") + def _compute_restrict_counterpart_partner(self): + for rec in self: + rec.restrict_counterpart_partner = ( + rec.company_id.payment_line_restrict_counterpart_partner + ) def _process_post_reconcile(self): for rec in self.filtered("line_payment_counterpart_ids"): @@ -290,7 +300,51 @@ class AccountPaymentCounterLine(models.Model): payment_id = fields.Many2one( "account.payment", string="Payment", ondelete="cascade" ) + counterpart_partner_domain = fields.Binary( + compute="_compute_counterpart_partner_domain", + help="Technical field holding the domain applied to the partner of the " + "counterpart line.", + ) def _get_onchange_fields(self): res = super()._get_onchange_fields() return res + ("payment_id.currency_id", "payment_id.date") + + @api.depends("payment_id.partner_id", "payment_id.restrict_counterpart_partner") + def _compute_counterpart_partner_domain(self): + for rec in self: + payment = rec.payment_id + if not payment.restrict_counterpart_partner: + rec.counterpart_partner_domain = [] + elif payment.partner_id: + commercial_partner = payment.partner_id.commercial_partner_id + rec.counterpart_partner_domain = [ + ("commercial_partner_id", "=", commercial_partner.id) + ] + else: + # Restriction is on but no partner is set yet: forbid selection + # instead of offering every partner. + rec.counterpart_partner_domain = [("id", "=", False)] + + @api.constrains("partner_id", "move_id", "aml_id", "payment_id") + def _check_counterpart_partner(self): + for rec in self: + payment = rec.payment_id + if not payment or not payment.restrict_counterpart_partner: + continue + payment_partner = payment.partner_id.commercial_partner_id + if not payment_partner: + continue + line_partners = ( + rec.partner_id.commercial_partner_id + | rec.move_id.commercial_partner_id + | rec.aml_id.partner_id.commercial_partner_id + ) + if line_partners and line_partners != payment_partner: + raise ValidationError( + self.env._( + "You can only apply counterpart lines of %(partner)s, " + "the partner set on the payment.", + partner=payment_partner.display_name, + ) + ) diff --git a/account_payment_line/models/res_company.py b/account_payment_line/models/res_company.py new file mode 100644 index 00000000000..02153b1e73c --- /dev/null +++ b/account_payment_line/models/res_company.py @@ -0,0 +1,13 @@ +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + payment_line_restrict_counterpart_partner = fields.Boolean( + string="Restrict counterpart lines to payment partner", + help="When enabled, counterpart lines of a payment can only use the " + "partner set on the payment header (its commercial entity). This " + "prevents applying invoices of a partner different from the one " + "registered in the payment.", + ) diff --git a/account_payment_line/models/res_config_settings.py b/account_payment_line/models/res_config_settings.py new file mode 100644 index 00000000000..07130e39539 --- /dev/null +++ b/account_payment_line/models/res_config_settings.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + payment_line_restrict_counterpart_partner = fields.Boolean( + related="company_id.payment_line_restrict_counterpart_partner", + readonly=False, + ) diff --git a/account_payment_line/readme/CONFIGURE.md b/account_payment_line/readme/CONFIGURE.md new file mode 100644 index 00000000000..fb3db09135c --- /dev/null +++ b/account_payment_line/readme/CONFIGURE.md @@ -0,0 +1,11 @@ +By default, counterpart lines let you pick any partner, so you can move +balances between partners. + +If you want to forbid that and make sure a payment only applies invoices +of the partner set on its header, go to **Invoicing / Settings** and enable +**Restrict counterpart lines to payment partner**. + +The setting is company dependent and disabled by default, so it does not +change the current behavior unless you activate it. Once enabled, the +partner of the counterpart lines is limited to the commercial entity of +the payment partner, both in the interface and on save. diff --git a/account_payment_line/readme/CONTRIBUTORS.md b/account_payment_line/readme/CONTRIBUTORS.md index e2daf8bf739..cfcc78b0edf 100644 --- a/account_payment_line/readme/CONTRIBUTORS.md +++ b/account_payment_line/readme/CONTRIBUTORS.md @@ -1,2 +1,3 @@ - Christopher Ormaza. \<\> - Do Anh Duy \<\> +- Ricardo Jara (Spearhead) \<\> diff --git a/account_payment_line/static/description/index.html b/account_payment_line/static/description/index.html index 4d197e35cde..f3e8df232af 100644 --- a/account_payment_line/static/description/index.html +++ b/account_payment_line/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Payment Counterpart Lines -
+
+

Payment Counterpart Lines

- - -Odoo Community Association - -
-

Payment Counterpart Lines

-

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

+

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

This module is an utility module to add lines in payment, allowing users make more complicated cases when processing payments, split on many invoices, set up specific write-off and adding some analytic information

@@ -382,18 +377,31 @@

Payment Counterpart Lines

Table of contents

+
+

Configuration

+

By default, counterpart lines let you pick any partner, so you can move +balances between partners.

+

If you want to forbid that and make sure a payment only applies invoices +of the partner set on its header, go to Invoicing / Settings and +enable Restrict counterpart lines to payment partner.

+

The setting is company dependent and disabled by default, so it does not +change the current behavior unless you activate it. Once enabled, the +partner of the counterpart lines is limited to the commercial entity of +the payment partner, both in the interface and on save.

+
-

Usage

+

Usage

You can use payment distribution suggestion, and if system found moves pending to reconcile related with partner selected, system will create all lines trying to pay all invoices until amount remain

@@ -401,7 +409,7 @@

Usage

payment works as a normal payment

-

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 @@ -409,22 +417,23 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ForgeFlow S.L.
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -439,6 +448,5 @@

Maintainers

-
diff --git a/account_payment_line/tests/test_account_payment_line.py b/account_payment_line/tests/test_account_payment_line.py index b3a42b57b2a..472ed765f6b 100644 --- a/account_payment_line/tests/test_account_payment_line.py +++ b/account_payment_line/tests/test_account_payment_line.py @@ -931,3 +931,66 @@ def test_11_exceptions(self): ], post=True, ) + + def test_12_restrict_counterpart_partner(self): + # Without the restriction (default) the domain is empty and lines of + # a different partner can be added. + self.assertFalse(self.env.company.payment_line_restrict_counterpart_partner) + other_invoice = self._create_invoice("out_invoice", self.customer2, 100.0) + payment = self._create_payment( + self.customer, + 100.0, + "inbound", + "customer", + [{"move_id": other_invoice}], + ) + self.assertEqual( + payment.line_payment_counterpart_ids.counterpart_partner_domain, [] + ) + payment.line_payment_counterpart_ids.unlink() + + # Enable the restriction at company level. + self.env.company.payment_line_restrict_counterpart_partner = True + + own_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + payment = self._create_payment( + self.customer, + 100.0, + "inbound", + "customer", + [{"move_id": own_invoice}], + post=True, + ) + self.assertEqual( + payment.line_payment_counterpart_ids.counterpart_partner_domain, + [("commercial_partner_id", "=", self.customer.commercial_partner_id.id)], + ) + self.assertEqual(payment.state, "paid") + + # Restriction enabled but no partner on the header yet: the selection + # is forbidden instead of falling back to every partner. + payment_without_partner = self.env["account.payment"].create( + { + "journal_id": self.bank_journal.id, + "payment_type": "inbound", + "partner_type": "customer", + "amount": 100.0, + } + ) + self.assertFalse(payment_without_partner.partner_id) + line_without_partner = self.env["account.payment.counterpart.line"].new( + {"payment_id": payment_without_partner.id} + ) + self.assertEqual( + line_without_partner.counterpart_partner_domain, [("id", "=", False)] + ) + + # Applying an invoice of a different partner must be forbidden. + with self.assertRaises(ValidationError): + self._create_payment( + self.customer, + 100.0, + "inbound", + "customer", + [{"move_id": other_invoice}], + ) diff --git a/account_payment_line/views/account_payment_views.xml b/account_payment_line/views/account_payment_views.xml index 9a62a81d1d3..5af18c9ad50 100644 --- a/account_payment_line/views/account_payment_views.xml +++ b/account_payment_line/views/account_payment_views.xml @@ -7,11 +7,13 @@ + + + + + res.config.settings.view.form.inherit.account.payment.line + res.config.settings + + + + + + + + + + + +