From 9616c9940dfb54cd5ac6ab4e5f647e2d98edeb5a Mon Sep 17 00:00:00 2001 From: aliya Date: Mon, 8 May 2023 07:50:06 +0000 Subject: [PATCH] [FIX] account: fix delete invoice rights MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 16.0 any user can delete a customer invoice/vendor bill even if it creates a sequence gap. This commit updates the rights and the warning message: - The deletion confirmation message contains a warning about the sequence gap - Only a Billing Administrator/Accountant can delete customer invoices/vendor bills creating the gap - Also, if the fiduciary mode is on (`quick_edit_mode`) it should be possible to delete invoices/bills regardless of the user group task-3284218 closes odoo/odoo#128556 X-original-commit: 2249f899049ef94b14b083c8db976662b868b026 Related: odoo/enterprise#44128 Signed-off-by: William André (wan) Signed-off-by: Aliya Tastemirova (alta) --- addons/account/i18n/account.pot | 17 +++++++++++ addons/account/models/account_move.py | 23 +++++++++++++++ .../account_move_form/account_move_form.js | 16 +++++++++++ .../account_move_service.js | 28 +++++++++++++++++++ .../components/bills_upload/bills_upload.js | 21 +++++++++++--- .../static/tests/account_move_form_tests.js | 4 +++ 6 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 addons/account/static/src/components/account_move_service/account_move_service.js diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot index bc02aff6450..bf50c961b10 100644 --- a/addons/account/i18n/account.pot +++ b/addons/account/i18n/account.pot @@ -13515,6 +13515,14 @@ msgstr "" msgid "This move will be posted at the accounting date: %(date)s" msgstr "" +#. module: account +#. odoo-javascript +#: code:addons/account/static/src/components/account_move_form/account_move_form.js:0 +#: code:addons/account/static/src/components/account_move_list/account_move_list.js:0 +#, python-format +msgid "This operation will create a gap in the sequence." +msgstr "" + #. module: account #. odoo-python #: code:addons/account/models/account_payment.py:0 @@ -14944,6 +14952,15 @@ msgstr "" msgid "You cannot delete an item linked to a posted entry." msgstr "" +#. module: account +#. odoo-python +#: code:addons/account/models/account_move.py:0 +#, python-format +msgid "" +"You cannot delete this entry, as it has already consumed a sequence number " +"and is not the last one in the chain. You should probably revert it instead." +msgstr "" + #. module: account #. odoo-python #: code:addons/account/models/res_config_settings.py:0 diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index b4c82154cb6..f277ca5841d 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -2460,6 +2460,29 @@ class AccountMove(models.Model): return res + def check_move_sequence_chain(self): + return self.filtered(lambda move: move.name != '/')._is_end_of_seq_chain() + + @api.ondelete(at_uninstall=False) + def _unlink_forbid_parts_of_chain(self): + """ For a user with Billing/Bookkeeper rights, when the fidu mode is deactivated, + moves with a sequence number can only be deleted if they are the last element of a chain of sequence. + If they are not, deleting them would create a gap. If the user really wants to do this, he still can + explicitly empty the 'name' field of the move; but we discourage that practice. + If a user is a Billing Administrator/Accountant or if fidu mode is activated, we show a warning, + but they can delete the moves even if it creates a sequence gap. + """ + if not ( + self.user_has_groups('account.group_account_manager') + or self.company_id.quick_edit_mode + or self._context.get('force_delete') + or self.check_move_sequence_chain() + ): + raise UserError(_( + "You cannot delete this entry, as it has already consumed a sequence number and is not the last one in the chain. " + "You should probably revert it instead." + )) + def unlink(self): self = self.with_context(skip_invoice_sync=True, dynamic_unlink=True) # no need to sync to delete everything self.line_ids.unlink() diff --git a/addons/account/static/src/components/account_move_form/account_move_form.js b/addons/account/static/src/components/account_move_form/account_move_form.js index 56f84e23195..d0189f65f20 100644 --- a/addons/account/static/src/components/account_move_form/account_move_form.js +++ b/addons/account/static/src/components/account_move_form/account_move_form.js @@ -6,6 +6,21 @@ import { Notebook } from "@web/core/notebook/notebook"; import { formView } from "@web/views/form/form_view"; import { FormCompiler } from "@web/views/form/form_compiler"; import { FormRenderer } from "@web/views/form/form_renderer"; +import { FormController } from '@web/views/form/form_controller'; +import { useService } from "@web/core/utils/hooks"; + +export class AccountMoveController extends FormController { + setup() { + super.setup(); + this.account_move_service = useService("account_move"); + } + + async deleteRecord() { + if ( !await this.account_move_service.addDeletionDialog(this, this.model.root.resId)) { + return super.deleteRecord(...arguments); + } + } +}; export class AccountMoveFormNotebook extends Notebook { async changeTabTo(page_id) { @@ -56,6 +71,7 @@ export const AccountMoveFormView = { ...formView, Renderer: AccountMoveFormRenderer, Compiler: AccountMoveFormCompiler, + Controller: AccountMoveController, }; registry.category("views").add("account_move_form", AccountMoveFormView); diff --git a/addons/account/static/src/components/account_move_service/account_move_service.js b/addons/account/static/src/components/account_move_service/account_move_service.js new file mode 100644 index 00000000000..99494fcab7c --- /dev/null +++ b/addons/account/static/src/components/account_move_service/account_move_service.js @@ -0,0 +1,28 @@ +/** @odoo-module **/ + +import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; +import { escape } from "@web/core/utils/strings"; +import { markup } from "@odoo/owl"; +import { registry } from "@web/core/registry"; + + +export const accountMove = { + dependencies: ["dialog", "orm"], + start(env, { dialog, orm }) { + return { + async addDeletionDialog(component, moveIds) { + const isMoveEndOfChain = await orm.call('account.move', 'check_move_sequence_chain', [moveIds]); + if (!isMoveEndOfChain) { + const message = env._t("This operation will create a gap in the sequence."); + const confirmationDialogProps = component.deleteConfirmationDialogProps; + confirmationDialogProps.body = markup(`
${escape(message)}
${escape(confirmationDialogProps.body)}`); + dialog.add(ConfirmationDialog, confirmationDialogProps); + return true; + } + return false; + } + } + } +} + +registry.category("services").add("account_move", accountMove); diff --git a/addons/account/static/src/components/bills_upload/bills_upload.js b/addons/account/static/src/components/bills_upload/bills_upload.js index e6cd0f34590..6edb49d4dd2 100644 --- a/addons/account/static/src/components/bills_upload/bills_upload.js +++ b/addons/account/static/src/components/bills_upload/bills_upload.js @@ -14,7 +14,7 @@ import { KanbanRecord } from "@web/views/kanban/kanban_record"; import { FileUploader } from "@web/views/fields/file_handler"; import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; -const { Component, useState } = owl; +import { Component, useState } from "@odoo/owl"; export class AccountFileUploader extends Component { setup() { @@ -135,15 +135,28 @@ AccountMoveUploadListRenderer.components = { AccountDropZone, }; -export class AccountMoveUploadListController extends ListController {} -AccountMoveUploadListController.components = { +export class AccountMoveListController extends ListController { + setup() { + super.setup(); + this.account_move_service = useService("account_move"); + } + + async onDeleteSelectedRecords() { + const selectedResIds = await this.getSelectedResIds(); + if (!await this.account_move_service.addDeletionDialog(this, selectedResIds)) { + return super.onDeleteSelectedRecords(...arguments); + } + } +}; + +AccountMoveListController.components = { ...ListController.components, AccountFileUploader, }; export const AccountMoveUploadListView = { ...listView, - Controller: AccountMoveUploadListController, + Controller: AccountMoveListController, Renderer: AccountMoveUploadListRenderer, buttonTemplate: "account.ListView.Buttons", }; diff --git a/addons/account/static/tests/account_move_form_tests.js b/addons/account/static/tests/account_move_form_tests.js index e83e3f180c1..d8ebe943046 100644 --- a/addons/account/static/tests/account_move_form_tests.js +++ b/addons/account/static/tests/account_move_form_tests.js @@ -4,6 +4,7 @@ import { start, startServer } from "@mail/../tests/helpers/test_utils"; import { patchWithCleanup } from "@web/../tests/helpers/utils"; import { AccountMoveFormRenderer } from "@account/components/account_move_form/account_move_form"; +import { accountMove as accountMoveService} from '@account/components/account_move_service/account_move_service'; QUnit.module("Views", {}, function (hooks) { QUnit.module('MoveFormView'); @@ -25,6 +26,9 @@ QUnit.module("Views", {}, function (hooks) { }; const { click, openView } = await start({ serverData: { views }, + services: { + 'account_move': accountMoveService, + } }); patchWithCleanup(AccountMoveFormRenderer.prototype, { saveBeforeTabChange() {