From d8af1b48931b98fdacaf263457465c671fd03b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Alan=20Ramos=20Rodr=C3=ADguez?= Date: Mon, 27 Jul 2026 16:53:03 -0600 Subject: [PATCH] [ADD] account_caba_payment_date: date cash basis entries on the payment date Standard Odoo dates the tax cash basis entry with the newest date of the reconciled entries and, when that date falls in a locked period, with the date of the day the reconciliation is done. This shifts cash basis taxes (e.g. VAT effectively paid) to a fiscal month different from the payment month, which is not acceptable in countries where those taxes are due on the payment date (e.g. Mexico). Date the entry on the bank/cash entry date of the reconciliation, with a configurable per-company policy when that date falls in a locked period: block the reconciliation (default), use the first open date, or keep the standard behavior. Co-Authored-By: Claude Fable 5 --- account_caba_payment_date/README.rst | 134 +++++ account_caba_payment_date/__init__.py | 4 + account_caba_payment_date/__manifest__.py | 17 + account_caba_payment_date/models/__init__.py | 6 + .../models/account_partial_reconcile.py | 68 +++ .../models/res_company.py | 25 + .../models/res_config_settings.py | 13 + account_caba_payment_date/pyproject.toml | 3 + account_caba_payment_date/readme/CONFIGURE.md | 8 + .../readme/CONTRIBUTORS.md | 2 + .../readme/DESCRIPTION.md | 20 + account_caba_payment_date/readme/USAGE.md | 6 + .../static/description/index.html | 480 ++++++++++++++++++ account_caba_payment_date/tests/__init__.py | 4 + .../tests/test_caba_payment_date.py | 111 ++++ .../views/res_config_settings_views.xml | 23 + 16 files changed, 924 insertions(+) create mode 100644 account_caba_payment_date/README.rst create mode 100644 account_caba_payment_date/__init__.py create mode 100644 account_caba_payment_date/__manifest__.py create mode 100644 account_caba_payment_date/models/__init__.py create mode 100644 account_caba_payment_date/models/account_partial_reconcile.py create mode 100644 account_caba_payment_date/models/res_company.py create mode 100644 account_caba_payment_date/models/res_config_settings.py create mode 100644 account_caba_payment_date/pyproject.toml create mode 100644 account_caba_payment_date/readme/CONFIGURE.md create mode 100644 account_caba_payment_date/readme/CONTRIBUTORS.md create mode 100644 account_caba_payment_date/readme/DESCRIPTION.md create mode 100644 account_caba_payment_date/readme/USAGE.md create mode 100644 account_caba_payment_date/static/description/index.html create mode 100644 account_caba_payment_date/tests/__init__.py create mode 100644 account_caba_payment_date/tests/test_caba_payment_date.py create mode 100644 account_caba_payment_date/views/res_config_settings_views.xml diff --git a/account_caba_payment_date/README.rst b/account_caba_payment_date/README.rst new file mode 100644 index 00000000000..5908c95d25c --- /dev/null +++ b/account_caba_payment_date/README.rst @@ -0,0 +1,134 @@ +========================= +Account CABA Payment Date +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9c7e59147d182b4debe2d4dc4d06c29c40b8f038f5e6069c14c20933271fc4be + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--payment-lightgray.png?logo=github + :target: https://github.com/OCA/account-payment/tree/17.0/account_caba_payment_date + :alt: OCA/account-payment +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-payment-17-0/account-payment-17-0-account_caba_payment_date + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/account-payment&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module sets the date of the tax cash basis (CABA) journal entry to +the real payment date instead of the date Odoo chooses. + +Standard Odoo dates the cash basis entry with the newest date of the +reconciled entries, and, when that date falls in a locked period, with +the date of the day the reconciliation is done. This shifts the cash +basis taxes (e.g. VAT effectively paid) to a fiscal month different from +the payment month, which is not acceptable in countries where those +taxes are due on the payment date (e.g. Mexico). + +With this module the cash basis entry is always dated on the date of the +bank/cash entry of the reconciliation. When that date falls in a locked +period, the behavior is configurable per company: + +- **Block the reconciliation** (default): raise an error asking to + reopen the period. +- **Use the first open date**: date the entry on the first day after the + lock date. +- **Keep the standard behavior**: let Odoo date the entry on the + reconciliation date. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure the behavior when the payment date falls in a locked +period: + +1. Go to *Accounting > Configuration > Settings*. +2. In the *Taxes* section, under *Cash Basis*, select the *Cash Basis + Lock Policy*: + + - *Block the reconciliation* (default) + - *Use the first open date* + - *Keep the standard behavior* + +Usage +===== + +- Create an invoice with a cash basis tax and register the payment. +- The generated cash basis entry is dated on the payment date, even if + the invoice or the reconciliation has a different date. +- If the payment date falls in a locked period, the configured lock + policy is applied (by default the reconciliation is blocked with an + explanatory error). + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Jarsa + +Contributors +------------ + +- `Jarsa `__: + + - Alan Ramos + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-alan196| image:: https://github.com/alan196.png?size=40px + :target: https://github.com/alan196 + :alt: alan196 + +Current `maintainer `__: + +|maintainer-alan196| + +This module is part of the `OCA/account-payment `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_caba_payment_date/__init__.py b/account_caba_payment_date/__init__.py new file mode 100644 index 00000000000..4c256a0efe6 --- /dev/null +++ b/account_caba_payment_date/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import models diff --git a/account_caba_payment_date/__manifest__.py b/account_caba_payment_date/__manifest__.py new file mode 100644 index 00000000000..831b92f3c6c --- /dev/null +++ b/account_caba_payment_date/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2024 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +{ + "name": "Account CABA Payment Date", + "version": "17.0.1.0.0", + "author": "Jarsa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-payment", + "license": "LGPL-3", + "category": "Accounting & Finance", + "summary": "Set CABA date based on payment date", + "depends": ["account"], + "data": ["views/res_config_settings_views.xml"], + "installable": True, + "development_status": "Alpha", + "maintainers": ["alan196"], +} diff --git a/account_caba_payment_date/models/__init__.py b/account_caba_payment_date/models/__init__.py new file mode 100644 index 00000000000..8c7e2962cb4 --- /dev/null +++ b/account_caba_payment_date/models/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2024 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import account_partial_reconcile +from . import res_company +from . import res_config_settings diff --git a/account_caba_payment_date/models/account_partial_reconcile.py b/account_caba_payment_date/models/account_partial_reconcile.py new file mode 100644 index 00000000000..650d2b3d7ea --- /dev/null +++ b/account_caba_payment_date/models/account_partial_reconcile.py @@ -0,0 +1,68 @@ +# Copyright 2024 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from datetime import date as date_lib +from datetime import timedelta + +from odoo import _, models +from odoo.exceptions import UserError + + +class AccountPartialReconcile(models.Model): + _inherit = "account.partial.reconcile" + + def _caba_get_payment_date(self): + """Return the real payment date of the reconciliation: the date of the + bank/cash journal entry when there is one, the newest date otherwise.""" + self.ensure_one() + for line in (self.debit_move_id, self.credit_move_id): + if line.journal_id.type in ("bank", "cash"): + return line.date + return self.max_date + + def _create_tax_cash_basis_moves(self): + moves = super()._create_tax_cash_basis_moves() + for move in moves: + partial = move.tax_cash_basis_rec_id + if not partial: + continue + date = partial._caba_get_payment_date() + # The tax lock date also applies: the cash basis entry affects the + # tax report, so it cannot be dated inside a tax-locked period. + lock_date = max( + move.company_id._get_user_fiscal_lock_date(), + move.company_id.max_tax_lock_date or date_lib.min, + ) + if date <= lock_date: + policy = move.company_id.caba_payment_date_lock_policy + if policy == "standard": + continue + if policy == "next_open": + date = lock_date + timedelta(days=1) + else: # block + raise UserError( + _( + "The cash basis entry of this reconciliation must be " + "dated on the payment date %(date)s, but that period " + "is locked " + "(lock date: %(lock_date)s).\n" + "Reopen the period, or change the cash basis lock policy " + "in the accounting settings.", + date=date, + lock_date=lock_date, + ) + ) + if date != move.date: + month_changed = (date.year, date.month) != ( + move.date.year, + move.date.month, + ) + vals = {"date": date} + if month_changed: + # Clear the name so the date-sequence constraint does not + # reject the write, then resequence for the new period. + vals["name"] = False + move.write(vals) + if month_changed: + move._compute_name() + return moves diff --git a/account_caba_payment_date/models/res_company.py b/account_caba_payment_date/models/res_company.py new file mode 100644 index 00000000000..2432c5d42d4 --- /dev/null +++ b/account_caba_payment_date/models/res_company.py @@ -0,0 +1,25 @@ +# Copyright 2026 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + caba_payment_date_lock_policy = fields.Selection( + [ + ("block", "Block the reconciliation"), + ("next_open", "Use the first open date"), + ("standard", "Keep the standard behavior"), + ], + default="block", + required=True, + string="Cash Basis Lock Policy", + help="What to do when the payment date of a cash basis entry falls in a " + "locked period:\n" + "- Block the reconciliation: raise an error asking to reopen the period.\n" + "- Use the first open date: date the entry on the first day after the lock.\n" + "- Keep the standard behavior: let Odoo date the entry on the " + "reconciliation date.", + ) diff --git a/account_caba_payment_date/models/res_config_settings.py b/account_caba_payment_date/models/res_config_settings.py new file mode 100644 index 00000000000..32b0c7868cd --- /dev/null +++ b/account_caba_payment_date/models/res_config_settings.py @@ -0,0 +1,13 @@ +# Copyright 2026 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + caba_payment_date_lock_policy = fields.Selection( + related="company_id.caba_payment_date_lock_policy", + readonly=False, + ) diff --git a/account_caba_payment_date/pyproject.toml b/account_caba_payment_date/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/account_caba_payment_date/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/account_caba_payment_date/readme/CONFIGURE.md b/account_caba_payment_date/readme/CONFIGURE.md new file mode 100644 index 00000000000..db3fbb98c5c --- /dev/null +++ b/account_caba_payment_date/readme/CONFIGURE.md @@ -0,0 +1,8 @@ +To configure the behavior when the payment date falls in a locked period: + +1. Go to *Accounting \> Configuration \> Settings*. +2. In the *Taxes* section, under *Cash Basis*, select the *Cash Basis Lock + Policy*: + - *Block the reconciliation* (default) + - *Use the first open date* + - *Keep the standard behavior* diff --git a/account_caba_payment_date/readme/CONTRIBUTORS.md b/account_caba_payment_date/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..6a1b8eca076 --- /dev/null +++ b/account_caba_payment_date/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Jarsa](https://www.jarsa.com): + - Alan Ramos \<\> diff --git a/account_caba_payment_date/readme/DESCRIPTION.md b/account_caba_payment_date/readme/DESCRIPTION.md new file mode 100644 index 00000000000..a22eb523831 --- /dev/null +++ b/account_caba_payment_date/readme/DESCRIPTION.md @@ -0,0 +1,20 @@ +This module sets the date of the tax cash basis (CABA) journal entry to the +real payment date instead of the date Odoo chooses. + +Standard Odoo dates the cash basis entry with the newest date of the +reconciled entries, and, when that date falls in a locked period, with the +date of the day the reconciliation is done. This shifts the cash basis +taxes (e.g. VAT effectively paid) to a fiscal month different from the +payment month, which is not acceptable in countries where those taxes are +due on the payment date (e.g. Mexico). + +With this module the cash basis entry is always dated on the date of the +bank/cash entry of the reconciliation. When that date falls in a locked +period, the behavior is configurable per company: + +- **Block the reconciliation** (default): raise an error asking to reopen + the period. +- **Use the first open date**: date the entry on the first day after the + lock date. +- **Keep the standard behavior**: let Odoo date the entry on the + reconciliation date. diff --git a/account_caba_payment_date/readme/USAGE.md b/account_caba_payment_date/readme/USAGE.md new file mode 100644 index 00000000000..651e43867a9 --- /dev/null +++ b/account_caba_payment_date/readme/USAGE.md @@ -0,0 +1,6 @@ +- Create an invoice with a cash basis tax and register the payment. +- The generated cash basis entry is dated on the payment date, even if the + invoice or the reconciliation has a different date. +- If the payment date falls in a locked period, the configured lock policy + is applied (by default the reconciliation is blocked with an explanatory + error). diff --git a/account_caba_payment_date/static/description/index.html b/account_caba_payment_date/static/description/index.html new file mode 100644 index 00000000000..64e43e15f10 --- /dev/null +++ b/account_caba_payment_date/static/description/index.html @@ -0,0 +1,480 @@ + + + + + +Account CABA Payment Date + + + +
+

Account CABA Payment Date

+ + +

Alpha License: LGPL-3 OCA/account-payment Translate me on Weblate Try me on Runboat

+

This module sets the date of the tax cash basis (CABA) journal entry to +the real payment date instead of the date Odoo chooses.

+

Standard Odoo dates the cash basis entry with the newest date of the +reconciled entries, and, when that date falls in a locked period, with +the date of the day the reconciliation is done. This shifts the cash +basis taxes (e.g. VAT effectively paid) to a fiscal month different from +the payment month, which is not acceptable in countries where those +taxes are due on the payment date (e.g. Mexico).

+

With this module the cash basis entry is always dated on the date of the +bank/cash entry of the reconciliation. When that date falls in a locked +period, the behavior is configurable per company:

+
    +
  • Block the reconciliation (default): raise an error asking to +reopen the period.
  • +
  • Use the first open date: date the entry on the first day after the +lock date.
  • +
  • Keep the standard behavior: let Odoo date the entry on the +reconciliation date.
  • +
+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

To configure the behavior when the payment date falls in a locked +period:

+
    +
  1. Go to Accounting > Configuration > Settings.
  2. +
  3. In the Taxes section, under Cash Basis, select the Cash Basis +Lock Policy:
      +
    • Block the reconciliation (default)
    • +
    • Use the first open date
    • +
    • Keep the standard behavior
    • +
    +
  4. +
+
+
+

Usage

+
    +
  • Create an invoice with a cash basis tax and register the payment.
  • +
  • The generated cash basis entry is dated on the payment date, even if +the invoice or the reconciliation has a different date.
  • +
  • If the payment date falls in a locked period, the configured lock +policy is applied (by default the reconciliation is blocked with an +explanatory error).
  • +
+
+
+

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 +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Jarsa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

alan196

+

This module is part of the OCA/account-payment project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/account_caba_payment_date/tests/__init__.py b/account_caba_payment_date/tests/__init__.py new file mode 100644 index 00000000000..a6a509232d7 --- /dev/null +++ b/account_caba_payment_date/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import test_caba_payment_date diff --git a/account_caba_payment_date/tests/test_caba_payment_date.py b/account_caba_payment_date/tests/test_caba_payment_date.py new file mode 100644 index 00000000000..9f39e2a6727 --- /dev/null +++ b/account_caba_payment_date/tests/test_caba_payment_date.py @@ -0,0 +1,111 @@ +# Copyright 2026 Jarsa (https://www.jarsa.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import Command, fields +from odoo.exceptions import UserError +from odoo.tests import tagged + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon + + +@tagged("post_install", "-at_install") +class TestCabaPaymentDate(AccountTestInvoicingCommon): + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref=chart_template_ref) + cls.company = cls.company_data["company"] + cls.company.tax_exigibility = True + cls.caba_transition_account = cls.env["account.account"].create( + { + "name": "CABA Transition", + "code": "250199", + "account_type": "liability_current", + "reconcile": True, + "company_id": cls.company.id, + } + ) + cls.caba_tax = cls.env["account.tax"].create( + { + "name": "Tax CABA 16%", + "amount": 16, + "type_tax_use": "sale", + "tax_exigibility": "on_payment", + "cash_basis_transition_account_id": cls.caba_transition_account.id, + "company_id": cls.company.id, + } + ) + + def _create_posted_invoice(self, invoice_date): + invoice = self.env["account.move"].create( + { + "move_type": "out_invoice", + "partner_id": self.partner_a.id, + "invoice_date": invoice_date, + "date": invoice_date, + "invoice_line_ids": [ + Command.create( + { + "product_id": self.product_a.id, + "quantity": 1, + "price_unit": 1000.0, + "tax_ids": [Command.set(self.caba_tax.ids)], + } + ) + ], + } + ) + invoice.action_post() + return invoice + + def _register_payment(self, invoice, payment_date): + return ( + self.env["account.payment.register"] + .with_context(active_model="account.move", active_ids=invoice.ids) + .create({"payment_date": payment_date}) + ._create_payments() + ) + + def _get_caba_moves(self, invoice): + return self.env["account.move"].search( + [("tax_cash_basis_origin_move_id", "=", invoice.id)] + ) + + def test_caba_uses_payment_date(self): + # The invoice is newer than the payment: standard Odoo would date the + # cash basis entry on the invoice date (newest reconciled date). + invoice = self._create_posted_invoice("2026-05-10") + self._register_payment(invoice, "2026-04-29") + caba = self._get_caba_moves(invoice) + self.assertTrue(caba) + self.assertEqual(caba.mapped("date"), [fields.Date.to_date("2026-04-29")]) + + def test_lock_policy_block(self): + invoice = self._create_posted_invoice("2026-01-15") + self.company.tax_lock_date = "2026-02-28" + with self.assertRaises(UserError): + self._register_payment(invoice, "2026-02-10") + + def test_lock_policy_next_open(self): + self.company.caba_payment_date_lock_policy = "next_open" + invoice = self._create_posted_invoice("2026-01-15") + self.company.tax_lock_date = "2026-02-28" + self._register_payment(invoice, "2026-02-10") + caba = self._get_caba_moves(invoice) + self.assertEqual(caba.mapped("date"), [fields.Date.to_date("2026-03-01")]) + + def test_lock_policy_standard(self): + self.company.caba_payment_date_lock_policy = "standard" + invoice = self._create_posted_invoice("2026-01-15") + self.company.tax_lock_date = "2026-02-28" + self._register_payment(invoice, "2026-02-10") + caba = self._get_caba_moves(invoice) + # Standard fallback: Odoo dates the entry out of the locked period + # (first open month), not on the payment date. + self.assertTrue(caba.date > fields.Date.to_date("2026-02-28")) + self.assertNotEqual(caba.date, fields.Date.to_date("2026-02-10")) + + def test_caba_name_follows_new_month(self): + invoice = self._create_posted_invoice("2026-05-10") + self._register_payment(invoice, "2026-04-29") + caba = self._get_caba_moves(invoice) + self.assertNotIn("/05/", caba.name) diff --git a/account_caba_payment_date/views/res_config_settings_views.xml b/account_caba_payment_date/views/res_config_settings_views.xml new file mode 100644 index 00000000000..136adbf5f79 --- /dev/null +++ b/account_caba_payment_date/views/res_config_settings_views.xml @@ -0,0 +1,23 @@ + + + + res.config.settings.view.form.inherit.caba.payment.date + res.config.settings + + + +
+
+
+
+
+
+
+