diff --git a/.docker_files/main/__manifest__.py b/.docker_files/main/__manifest__.py
index f5509e30..9d1f3be6 100644
--- a/.docker_files/main/__manifest__.py
+++ b/.docker_files/main/__manifest__.py
@@ -15,6 +15,7 @@
"account_bank_menu",
"account_closing_journal",
"account_invoice_constraint_chronology_forced",
+ "account_payment_cancel_group",
"account_show_full_features",
"invoice_refund_not_earlier",
"old_accounts",
diff --git a/Dockerfile b/Dockerfile
index 9b88eade..b242319b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,13 +9,16 @@ RUN pip3 install -r requirements.txt
ENV THIRD_PARTY_ADDONS /mnt/third-party-addons
RUN mkdir -p "${THIRD_PARTY_ADDONS}" && chown -R odoo "${THIRD_PARTY_ADDONS}"
COPY ./gitoo.yml /gitoo.yml
-RUN gitoo install-all --conf_file /gitoo.yml --destination "${THIRD_PARTY_ADDONS}"
+RUN if [ -s /gitoo.yml ]; then \
+ gitoo install-all --conf_file /gitoo.yml --destination "${THIRD_PARTY_ADDONS}"; \
+ fi
USER odoo
COPY account_bank_menu /mnt/extra-addons/account_bank_menu
COPY account_closing_journal /mnt/extra-addons/account_closing_journal
COPY account_invoice_constraint_chronology_forced /mnt/extra-addons/account_invoice_constraint_chronology_forced
+COPY account_payment_cancel_group /mnt/extra-addons/account_payment_cancel_group
COPY account_show_full_features /mnt/extra-addons/account_show_full_features
COPY invoice_refund_not_earlier /mnt/extra-addons/invoice_refund_not_earlier
COPY old_accounts /mnt/extra-addons/old_accounts
diff --git a/account_payment_cancel_group/README.rst b/account_payment_cancel_group/README.rst
new file mode 100644
index 00000000..fa3a6d38
--- /dev/null
+++ b/account_payment_cancel_group/README.rst
@@ -0,0 +1,34 @@
+====================
+Payment Cancel Group
+====================
+
+.. contents:: Table of Contents
+
+Context
+-------
+In vanilla Odoo, when the module account_cancel is installed, users with basic accounting access
+are able to cancel a payment (if authorized on the journal).
+
+.. image:: static/description/payment_cancel_button.png
+
+.. image:: static/description/payment_cancelled_message.png
+
+Summary
+-------
+This module adds a user group allowed to cancel payments.
+
+.. image:: static/description/user_form.png
+
+The `Cancel` button on payments is only displayed for members of this group.
+
+Known Issues
+~~~~~~~~~~~~
+As of Odoo version 14.0, the cancellation of payments passes through the status ``Draft``.
+
+In other words, a posted payment can be reset to draft, then it can be cancelled.
+
+Because of this new behavior, the module now restricts both ``Reset To Draft`` and ``Cancel`` buttons.
+
+Contributors
+------------
+* Numigi (tm) and all its contributors (https://bit.ly/numigiens)
diff --git a/account_payment_cancel_group/__init__.py b/account_payment_cancel_group/__init__.py
new file mode 100644
index 00000000..63bd6ae3
--- /dev/null
+++ b/account_payment_cancel_group/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from . import models
diff --git a/account_payment_cancel_group/__manifest__.py b/account_payment_cancel_group/__manifest__.py
new file mode 100644
index 00000000..f299d23d
--- /dev/null
+++ b/account_payment_cancel_group/__manifest__.py
@@ -0,0 +1,21 @@
+# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+{
+ "name": "Payment Cancel Group",
+ "version": "16.0.1.0.0",
+ "author": "Numigi",
+ "maintainer": "Numigi",
+ "website": "https://www.numigi.com",
+ "license": "LGPL-3",
+ "category": "Accounting",
+ "summary": "Add a user group allowed to cancel payments",
+ "depends": [
+ "account",
+ ],
+ "data": [
+ "security/res_groups.xml",
+ "views/account_payment.xml",
+ ],
+ "installable": True,
+}
diff --git a/account_payment_cancel_group/i18n/fr.po b/account_payment_cancel_group/i18n/fr.po
new file mode 100644
index 00000000..f3c8e224
--- /dev/null
+++ b/account_payment_cancel_group/i18n/fr.po
@@ -0,0 +1,52 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * account_payment_cancel_group
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 16.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2024-06-21 20:40+0000\n"
+"PO-Revision-Date: 2024-06-21 15:40-0500\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: \n"
+"X-Generator: Poedit 2.3\n"
+
+#. module: account_payment_cancel_group
+#: model:res.groups,name:account_payment_cancel_group.group_cancel_payments
+msgid "Cancel Payments"
+msgstr "Annulation de paiements"
+
+#. module: account_payment_cancel_group
+#: model:ir.model.fields,field_description:account_payment_cancel_group.field_account_move__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: account_payment_cancel_group
+#: model:ir.model.fields,field_description:account_payment_cancel_group.field_account_move__id
+msgid "ID"
+msgstr ""
+
+#. module: account_payment_cancel_group
+#: model:ir.model,name:account_payment_cancel_group.model_account_move
+msgid "Journal Entry"
+msgstr ""
+
+#. module: account_payment_cancel_group
+#: model:ir.model.fields,field_description:account_payment_cancel_group.field_account_move____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: account_payment_cancel_group
+#: code:addons/account_payment_cancel_group/models/account_move.py:0
+#, python-format
+msgid "You are not authorized to reset to draft or cancel payments."
+msgstr "Vous n'êtes pas autorisé à remettre à brouillon ou annuler des paiements."
+
+#~ msgid "Payment Cancelled"
+#~ msgstr "Paiement annulé"
diff --git a/account_payment_cancel_group/models/__init__.py b/account_payment_cancel_group/models/__init__.py
new file mode 100644
index 00000000..ff1eb517
--- /dev/null
+++ b/account_payment_cancel_group/models/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from . import account_move
diff --git a/account_payment_cancel_group/models/account_move.py b/account_payment_cancel_group/models/account_move.py
new file mode 100644
index 00000000..045ff4d5
--- /dev/null
+++ b/account_payment_cancel_group/models/account_move.py
@@ -0,0 +1,34 @@
+# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from odoo import models, _
+from odoo.exceptions import AccessError
+
+
+class AccountMove(models.Model):
+
+ _inherit = "account.move"
+
+ def button_draft(self):
+ self._check_payment_cancel_authorization()
+ return super().button_draft()
+
+ def button_cancel(self):
+ self._check_payment_cancel_authorization()
+ return super().button_cancel()
+
+ def _check_payment_cancel_authorization(self):
+ if self._contains_payments() and not self._user_can_cancel_payments():
+ raise AccessError(
+ _("You are not authorized to reset to draft or cancel payments.")
+ )
+
+ def _user_can_cancel_payments(self):
+ return self.env.user.has_group(
+ "account_payment_cancel_group.group_cancel_payments"
+ )
+
+ def _contains_payments(self):
+ return self and bool(
+ self.env["account.payment"].search([("move_id", "in", self.ids)])
+ )
diff --git a/account_payment_cancel_group/security/res_groups.xml b/account_payment_cancel_group/security/res_groups.xml
new file mode 100644
index 00000000..2be54a84
--- /dev/null
+++ b/account_payment_cancel_group/security/res_groups.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ Cancel Payments
+
+
+
+
diff --git a/account_payment_cancel_group/static/description/icon.png b/account_payment_cancel_group/static/description/icon.png
new file mode 100644
index 00000000..92a86b10
Binary files /dev/null and b/account_payment_cancel_group/static/description/icon.png differ
diff --git a/account_payment_cancel_group/static/description/payment_cancel_button.png b/account_payment_cancel_group/static/description/payment_cancel_button.png
new file mode 100644
index 00000000..8a37af30
Binary files /dev/null and b/account_payment_cancel_group/static/description/payment_cancel_button.png differ
diff --git a/account_payment_cancel_group/static/description/payment_cancelled_message.png b/account_payment_cancel_group/static/description/payment_cancelled_message.png
new file mode 100644
index 00000000..d6f5459f
Binary files /dev/null and b/account_payment_cancel_group/static/description/payment_cancelled_message.png differ
diff --git a/account_payment_cancel_group/static/description/user_form.png b/account_payment_cancel_group/static/description/user_form.png
new file mode 100644
index 00000000..9c1722f8
Binary files /dev/null and b/account_payment_cancel_group/static/description/user_form.png differ
diff --git a/account_payment_cancel_group/tests/__init__.py b/account_payment_cancel_group/tests/__init__.py
new file mode 100644
index 00000000..f8b1d4c0
--- /dev/null
+++ b/account_payment_cancel_group/tests/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from . import test_payment_cancel
diff --git a/account_payment_cancel_group/tests/test_payment_cancel.py b/account_payment_cancel_group/tests/test_payment_cancel.py
new file mode 100644
index 00000000..d036b170
--- /dev/null
+++ b/account_payment_cancel_group/tests/test_payment_cancel.py
@@ -0,0 +1,65 @@
+# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+import pytest
+from odoo.tests import common
+from odoo.exceptions import AccessError
+
+
+class TestPaymentCancel(common.SavepointCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.user = cls.env["res.users"].create(
+ {
+ "name": "Test User",
+ "email": "test@test.com",
+ "login": "test@test.com",
+ "groups_id": [
+ (4, cls.env.ref("account.group_account_manager").id),
+ ],
+ }
+ )
+
+ cls.journal = cls.env["account.journal"].create(
+ {
+ "name": "Test Bank Journal",
+ "type": "bank",
+ "code": "TEST",
+ }
+ )
+
+ cls.supplier = cls.env["res.partner"].create({"name": "Supplier"})
+ cls.payment = cls.env["account.payment"].create(
+ {
+ "journal_id": cls.journal.id,
+ "partner_id": cls.supplier.id,
+ "amount": 100,
+ "payment_type": "outbound",
+ "payment_method_id": cls.env.ref(
+ "account.account_payment_method_manual_out"
+ ).id,
+ "partner_type": "supplier",
+ }
+ )
+
+ def test_if_not_member_of_group__action_draft_not_allowed(self):
+ with pytest.raises(AccessError):
+ self.payment.with_user(self.user).action_draft()
+
+ def test_if_not_member_of_group__action_cancel_not_allowed(self):
+ with pytest.raises(AccessError):
+ self.payment.with_user(self.user).action_cancel()
+
+ def test_if_member_of_group__user_allowed(self):
+ self.user.groups_id |= self.env.ref(
+ "account_payment_cancel_group.group_cancel_payments"
+ )
+ self.payment.with_user(self.user).action_draft()
+ assert self.payment.state == "draft"
+ self.payment.with_user(self.user).action_cancel()
+ assert self.payment.state == "cancel"
+
+ def test_call_method_with_empty_recordset(self):
+ self.env["account.payment"].with_user(self.user).action_draft()
diff --git a/account_payment_cancel_group/views/account_payment.xml b/account_payment_cancel_group/views/account_payment.xml
new file mode 100644
index 00000000..1009723c
--- /dev/null
+++ b/account_payment_cancel_group/views/account_payment.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ Payment Form: restrict Cancel button
+ account.payment
+
+
+
+
+
+
+
+