[FIX] account: fix delete invoice rights

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) <wan@odoo.com>
Signed-off-by: Aliya Tastemirova (alta) <alta@odoo.com>
This commit is contained in:
aliya
2023-07-15 13:05:51 +02:00
parent 9bacc4e25d
commit 9616c9940d
6 changed files with 105 additions and 4 deletions
+17
View File
@@ -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
+23
View File
@@ -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()
@@ -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);
@@ -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(`<div class="text-danger">${escape(message)}</div>${escape(confirmationDialogProps.body)}`);
dialog.add(ConfirmationDialog, confirmationDialogProps);
return true;
}
return false;
}
}
}
}
registry.category("services").add("account_move", accountMove);
@@ -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",
};
@@ -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() {