diff --git a/.docker_files/main/__manifest__.py b/.docker_files/main/__manifest__.py index 7c50ec22..ead53acc 100644 --- a/.docker_files/main/__manifest__.py +++ b/.docker_files/main/__manifest__.py @@ -17,6 +17,7 @@ "account_fiscalyear_end_on_company", "account_invoice_constraint_chronology_forced", "account_move_reversal_access", + "account_move_unique_reversal", "account_negative_debit_credit", "account_payment_cancel_group", "account_show_full_features", diff --git a/Dockerfile b/Dockerfile index 61e1a02b..b99d767d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ COPY account_closing_journal /mnt/extra-addons/account_closing_journal COPY account_fiscalyear_end_on_company /mnt/extra-addons/account_fiscalyear_end_on_company COPY account_invoice_constraint_chronology_forced /mnt/extra-addons/account_invoice_constraint_chronology_forced COPY account_move_reversal_access /mnt/extra-addons/account_move_reversal_access +COPY account_move_unique_reversal /mnt/extra-addons/account_move_unique_reversal COPY account_negative_debit_credit /mnt/extra-addons/account_negative_debit_credit COPY account_payment_cancel_group /mnt/extra-addons/account_payment_cancel_group COPY account_show_full_features /mnt/extra-addons/account_show_full_features diff --git a/account_move_unique_reversal/README.rst b/account_move_unique_reversal/README.rst new file mode 100644 index 00000000..a9ddd577 --- /dev/null +++ b/account_move_unique_reversal/README.rst @@ -0,0 +1,27 @@ +============================ +Account Move Unique Reversal +============================ + +This module blocks the users from reversing the Journal Entries if they have already +been reversed or they are the reversal of another Entry except in cases where the journal is related to sales or purchases. + +- Reversed Entry: + +.. image:: static/description/reverse_reversed_move.png + +- Reversal Entry: + +.. image:: static/description/reverse_reversal_move.png + +Configuration +------------- +No configuration required apart from module installation. + +Contributors +------------ +* Numigi (tm) and all its contributors (https://bit.ly/numigiens) +* Komit (https://komit-consulting.com) + +More information +---------------- +* Meet us at https://bit.ly/numigi-com diff --git a/account_move_unique_reversal/__init__.py b/account_move_unique_reversal/__init__.py new file mode 100644 index 00000000..d6a63ca0 --- /dev/null +++ b/account_move_unique_reversal/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/account_move_unique_reversal/__manifest__.py b/account_move_unique_reversal/__manifest__.py new file mode 100644 index 00000000..f9e69be7 --- /dev/null +++ b/account_move_unique_reversal/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Account Move Unique Reversal", + "summary": "Blocks the users from reversing the Journal Entries if they have " + "already been reversed or they are the reversal of the other Entries", + "version": "16.0.1.0.0", + "website": "https://bit.ly/numigi-com", + "author": "Numigi", + "maintainer": "Numigi", + "license": "AGPL-3", + "depends": ["account"], + "installable": True, +} diff --git a/account_move_unique_reversal/i18n/fr.po b/account_move_unique_reversal/i18n/fr.po new file mode 100644 index 00000000..e104c0f0 --- /dev/null +++ b/account_move_unique_reversal/i18n/fr.po @@ -0,0 +1,33 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_move_unique_reversal +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-06-14 10:18+0000\n" +"PO-Revision-Date: 2024-06-14 10:18+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_move_unique_reversal +#: model:ir.model,name:account_move_unique_reversal.model_account_move_reversal +msgid "Account Move Reversal" +msgstr "Renversement de la pièce comptable" + +#. module: account_move_unique_reversal +#: code:addons/account_move_unique_reversal/models/account_move_reversal.py:18 +#, python-format +msgid "The accounting entry {} is already reversed (by entry {}). You can only reverse an accounting entry once." +msgstr "La pièce comptable {} est déjà renversée (pièce {}). Vous ne pouvez renverser une pièce qu’une seule fois." + +#. module: account_move_unique_reversal +#: code:addons/account_move_unique_reversal/models/account_move_reversal.py:28 +#, python-format +msgid "The accounting entry {} is the reversal of another entry ({}). You can not reverse a reversal accounting entry." +msgstr "La pièce comptable {} est le renversement d’une autre pièce comptable ({}). Vous ne pouvez pas renverser une pièce comptable qui est le renversement d’une autre pièce." diff --git a/account_move_unique_reversal/models/__init__.py b/account_move_unique_reversal/models/__init__.py new file mode 100644 index 00000000..1f6ed9aa --- /dev/null +++ b/account_move_unique_reversal/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import account_move diff --git a/account_move_unique_reversal/models/account_move.py b/account_move_unique_reversal/models/account_move.py new file mode 100644 index 00000000..fc505718 --- /dev/null +++ b/account_move_unique_reversal/models/account_move.py @@ -0,0 +1,33 @@ +# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, models +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = "account.move" + + def _reverse_moves(self, default_values_list=None, cancel=False): + for move in self: + if move.reversal_move_id and move.journal_id.type not in ( + "sale", + "purchase", + ): + raise UserError( + _( + "The accounting entry {} is already reversed (by entry {}). " + "You can only reverse an accounting entry once." + ).format(move.display_name, move.reversal_move_id.display_name) + ) + if move.reversed_entry_id and move.journal_id.type not in ( + "sale", + "purchase", + ): + raise UserError( + _( + "The accounting entry {} is the reversal of another entry " + "({}). You can not reverse a reversal accounting entry." + ).format(move.display_name, move.reversed_entry_id.display_name) + ) + return super()._reverse_moves(default_values_list, cancel) diff --git a/account_move_unique_reversal/static/description/icon.png b/account_move_unique_reversal/static/description/icon.png new file mode 100644 index 00000000..92a86b10 Binary files /dev/null and b/account_move_unique_reversal/static/description/icon.png differ diff --git a/account_move_unique_reversal/static/description/reverse_reversal_move.png b/account_move_unique_reversal/static/description/reverse_reversal_move.png new file mode 100644 index 00000000..1adc06d2 Binary files /dev/null and b/account_move_unique_reversal/static/description/reverse_reversal_move.png differ diff --git a/account_move_unique_reversal/static/description/reverse_reversed_move.png b/account_move_unique_reversal/static/description/reverse_reversed_move.png new file mode 100644 index 00000000..5bfa7234 Binary files /dev/null and b/account_move_unique_reversal/static/description/reverse_reversed_move.png differ diff --git a/account_move_unique_reversal/tests/__init__.py b/account_move_unique_reversal/tests/__init__.py new file mode 100644 index 00000000..365bf5dc --- /dev/null +++ b/account_move_unique_reversal/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_account_move_unique_reversal diff --git a/account_move_unique_reversal/tests/test_account_move_unique_reversal.py b/account_move_unique_reversal/tests/test_account_move_unique_reversal.py new file mode 100644 index 00000000..e5b2a4eb --- /dev/null +++ b/account_move_unique_reversal/tests/test_account_move_unique_reversal.py @@ -0,0 +1,109 @@ +# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests import common + + +class TestAccountMoveUniqueReversal(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.journal = cls.env["account.journal"].create( + {"name": "Test", "code": "TEST", "type": "general"} + ) + cls.journal_sale = cls.env["account.journal"].create( + { + "name": "Test Sales Journal", + "code": "tSAL", + "type": "sale", + } + ) + cls.journal_purchase = cls.env["account.journal"].create( + { + "name": "Test Purchase Journal", + "code": "tPO", + "type": "purchase", + } + ) + cls.today = fields.date.today() + cls.account_1 = cls.env["account.account"].create( + { + "name": "Account 1", + "code": "501001", + "account_type": "expense", + } + ) + cls.account_2 = cls.env["account.account"].create( + { + "name": "Account 2", + "code": "101001", + "account_type": "asset_fixed", + } + ) + cls.move = cls._create_invoice(cls.journal) + cls.move.action_post() + + cls.move_sale = cls._create_invoice(cls.journal_sale) + cls.move_sale.action_post() + + cls.move_purchase = cls._create_invoice(cls.journal_purchase) + cls.move_purchase.action_post() + + @classmethod + def _create_invoice(cls, journal): + move = cls.env["account.move"].create( + { + "journal_id": journal.id, + "date": cls.today, + "line_ids": [ + (0, 0, {"account_id": cls.account_1.id, "name": "/", "debit": 75}), + (0, 0, {"account_id": cls.account_1.id, "name": "/", "debit": 25}), + ( + 0, + 0, + {"account_id": cls.account_2.id, "name": "/", "credit": 100}, + ), + ], + } + ) + return move + + def test_reverse_reversed_entry_fail(self): + self._reverse_move(self.move) + with self.assertRaises(UserError): + self._reverse_move(self.move) + + def test_reverse_reversed_entry_sale_pass(self): + self._reverse_move(self.move_sale) + assert self._reverse_move(self.move_sale) + + def test_reverse_reversed_entry_purchase_pass(self): + self._reverse_move(self.move_purchase) + assert self._reverse_move(self.move_purchase) + + def test_reverse_reversal_entry_fail(self): + self._reverse_move(self.move) + reversal_move = self.move.reversal_move_id + with self.assertRaises(UserError): + self._reverse_move(reversal_move) + + def test_reverse_reversal_entry_sale_pass(self): + self._reverse_move(self.move_sale) + reversal_move = self.move_sale.reversal_move_id + assert self._reverse_move(reversal_move) + + def test_reverse_reversal_entry_purchase_pass(self): + self._reverse_move(self.move_purchase) + reversal_move = self.move_purchase.reversal_move_id + assert self._reverse_move(reversal_move) + + def _reverse_move(self, move): + wizard_env = self.env["account.move.reversal"] + wizard_env = wizard_env.with_context( + active_ids=[move.id], active_model="account.move" + ) + return wizard_env.create( + {"date": self.today, "journal_id": move.journal_id.id} + ).reverse_moves()