diff --git a/addons/web/static/src/views/form/form_controller.js b/addons/web/static/src/views/form/form_controller.js index ce8a35d8744..a193b04238f 100644 --- a/addons/web/static/src/views/form/form_controller.js +++ b/addons/web/static/src/views/form/form_controller.js @@ -426,10 +426,15 @@ export class FormController extends Component { async shouldExecuteAction(item) { const dirty = await this.model.root.isDirty(); if ((dirty || this.model.root.isNew) && !item.skipSave) { - return this.model.root.save({ + let hasError = false; + const isSaved = await this.model.root.save({ stayInEdition: true, - onError: this.onSaveError.bind(this), + onError: (...args) => { + hasError = true; + return this.onSaveError(...args); + }, }); + return isSaved && !hasError; } return true; } diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index a0de703b600..5b7932a6a61 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -4557,6 +4557,33 @@ QUnit.module("Views", (hooks) => { ); }); + QUnit.test("don't duplicate if save fail", async function (assert) { + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: '
', + actionMenus: {}, + async mockRPC(route, args) { + if (args.method === "create") { + assert.step("create"); + throw makeServerError(); + } + if (args.method === "copy") { + assert.step("copy"); + } + }, + }); + await editInput(target, "[name=foo] input", "new value"); + await toggleActionMenu(target); + await toggleMenuItem(target, "Duplicate"); + assert.containsOnce(target, ".modal .o_error_dialog"); + // Discard changes don't trigger Duplicate action + await click(target, ".modal .btn-secondary"); + + assert.verifySteps(["create"]); + }); + QUnit.test("clicking on stat buttons in edit mode", async function (assert) { let count = 0; const fakeActionService = {