From e1c088a8ce9fee816ab856906eedf854d94a02c4 Mon Sep 17 00:00:00 2001 From: Joseph Caburnay Date: Tue, 28 Feb 2023 10:35:53 +0000 Subject: [PATCH] [FIX] web: auto save record when changing value of 'toggle' fields Before 16.0 and the "always edit" form views, some fields allowed to be edited in readonly (e.g. the statusbar in crm lead or project task). Users could thus change the stage of a record in readonly and the change was saved directly. If the field was tracked, the change was even logged in the chatter directly. Since the form view is always in edition now, we loose that behavior and the user must click on the save cloud icon to manually save and see the tracking messages. To mitigate this, those fields that were editable in readonly could save the record directly when edited, like buttons do. Changing the values of the following fields should automatically save the record. - BooleanToggleField - PriorityField - StateSelectionField - StatusBarField To accomplish this, we now unconditionally save the record when the above mentioned fields are changed. closes odoo/odoo#115500 Task-id: 3175672 X-original-commit: c9b7c43c69eaf87e7900801b926d3868adc36d8c Signed-off-by: Aaron Bohy (aab) Signed-off-by: Joseph Caburnay (jcb) --- .../crm/static/tests/crm_rainbowman_tests.js | 8 ++--- .../boolean_toggle/boolean_toggle_field.js | 9 +----- .../list_boolean_toggle_field.js | 5 ++-- .../views/fields/priority/priority_field.js | 9 +----- .../state_selection/state_selection_field.js | 13 +++------ .../views/fields/statusbar/statusbar_field.js | 19 +++--------- .../fields/boolean_toggle_field_tests.js | 20 +++++++++++++ .../views/fields/one2many_field_tests.js | 2 +- .../views/fields/priority_field_tests.js | 28 ++++++++++++++++++ .../fields/state_selection_field_tests.js | 29 +++++++++++++++++++ .../views/fields/statusbar_field_tests.js | 25 ++++++++++++++++ .../static/tests/views/kanban_view_tests.js | 6 ++-- .../web/static/tests/views/list_view_tests.js | 14 +-------- 13 files changed, 123 insertions(+), 64 deletions(-) diff --git a/addons/crm/static/tests/crm_rainbowman_tests.js b/addons/crm/static/tests/crm_rainbowman_tests.js index 29f40e99dca..7dec19e7574 100644 --- a/addons/crm/static/tests/crm_rainbowman_tests.js +++ b/addons/crm/static/tests/crm_rainbowman_tests.js @@ -4,7 +4,6 @@ import "@crm/../tests/mock_server"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { click, - clickSave, dragAndDrop, getFixture, } from '@web/../tests/helpers/utils'; @@ -132,8 +131,8 @@ QUnit.module('Crm Rainbowman Triggers', { assert.verifySteps(['Go, go, go! Congrats for your first deal.']); }); - QUnit.test("first lead won, click on statusbar in edit mode then save", async function (assert) { - assert.expect(3); + QUnit.test("first lead won, click on statusbar in edit mode", async function (assert) { + assert.expect(2); await makeView({ ...this.testFormView, @@ -142,9 +141,6 @@ QUnit.module('Crm Rainbowman Triggers', { }); await click(target.querySelector(".o_statusbar_status button[data-value='3']")); - assert.verifySteps([]); // no message displayed yet - - await clickSave(target); assert.verifySteps(['Go, go, go! Congrats for your first deal.']); }); diff --git a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js index 8e3bda722e4..0791d5ac251 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js +++ b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js @@ -13,14 +13,7 @@ export class BooleanToggleField extends BooleanField { async onChange(newValue) { await this.props.record.update({ [this.props.name]: newValue }); - const rootRecord = - this.props.record.model.root instanceof this.props.record.constructor && - this.props.record.model.root; - const isInEdition = rootRecord ? rootRecord.isInEdition : this.props.record.isInEdition; - // We save only if we're on view mode readonly and no readonly field modifier - if (!isInEdition) { - return this.props.record.save(); - } + return this.props.record.save(); } } diff --git a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js index 9c6c321cb10..18474c2743a 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js +++ b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js @@ -6,11 +6,12 @@ import { booleanToggleField, BooleanToggleField } from "./boolean_toggle_field"; export class ListBooleanToggleField extends BooleanToggleField { static template = "web.ListBooleanToggleField"; - onClick() { + async onClick() { if (!this.props.readonly) { - this.props.record.update({ + await this.props.record.update({ [this.props.name]: !this.props.record.data[this.props.name], }); + return this.props.record.save(); } } } diff --git a/addons/web/static/src/views/fields/priority/priority_field.js b/addons/web/static/src/views/fields/priority/priority_field.js index df734fdf5ee..74b7c193d2a 100644 --- a/addons/web/static/src/views/fields/priority/priority_field.js +++ b/addons/web/static/src/views/fields/priority/priority_field.js @@ -85,14 +85,7 @@ export class PriorityField extends Component { async updateRecord(value) { await this.props.record.update({ [this.props.name]: value }); - const rootRecord = - this.props.record.model.root instanceof this.props.record.constructor && - this.props.record.model.root; - const isInEdition = rootRecord ? rootRecord.isInEdition : this.props.record.isInEdition; - // We save only if we're on view mode readonly and no readonly field modifier - if (!isInEdition) { - return this.props.record.save(); - } + return this.props.record.save(); } } diff --git a/addons/web/static/src/views/fields/state_selection/state_selection_field.js b/addons/web/static/src/views/fields/state_selection/state_selection_field.js index 284fa65f387..595b929708d 100644 --- a/addons/web/static/src/views/fields/state_selection/state_selection_field.js +++ b/addons/web/static/src/views/fields/state_selection/state_selection_field.js @@ -36,7 +36,9 @@ export class StateSelectionField extends Component { for (const [index, [value, label]] of this.options.entries()) { useCommand( sprintf(this.env._t("Set kanban state as %s"), label), - () => this.updateRecord(value), + () => { + this.updateRecord(value); + }, { category: "smart_action", hotkey: "alt+" + hotkeys[index], @@ -73,14 +75,7 @@ export class StateSelectionField extends Component { async updateRecord(value) { await this.props.record.update({ [this.props.name]: value }); - const rootRecord = - this.props.record.model.root instanceof this.props.record.constructor && - this.props.record.model.root; - const isInEdition = rootRecord ? rootRecord.isInEdition : this.props.record.isInEdition; - // We save only if we're on view mode readonly and no readonly field modifier - if (!isInEdition) { - return this.props.record.save(); - } + return this.props.record.save(); } } diff --git a/addons/web/static/src/views/fields/statusbar/statusbar_field.js b/addons/web/static/src/views/fields/statusbar/statusbar_field.js index 1483f3c225c..ac97cac1992 100644 --- a/addons/web/static/src/views/fields/statusbar/statusbar_field.js +++ b/addons/web/static/src/views/fields/statusbar/statusbar_field.js @@ -201,27 +201,16 @@ export class StatusBarField extends Component { } } - selectItem(item) { - const rootRecord = - this.props.record.model.root instanceof this.props.record.constructor && - this.props.record.model.root; - const isInEdition = rootRecord ? rootRecord.isInEdition : this.props.record.isInEdition; + async selectItem(item) { switch (this.props.record.fields[this.props.name].type) { case "many2one": - this.props.record.update({ [this.props.name]: [item.id, item.name] }); - // We save only if we're on view mode readonly and no readonly field modifier - if (!isInEdition) { - return this.props.record.save(); - } + await this.props.record.update({ [this.props.name]: [item.id, item.name] }); break; case "selection": - this.props.record.update({ [this.props.name]: item.id }); - // We save only if we're on view mode readonly and no readonly field modifier - if (!isInEdition) { - return this.props.record.save(); - } + await this.props.record.update({ [this.props.name]: item.id }); break; } + return this.props.record.save(); } onDropdownItemSelected(ev) { diff --git a/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js b/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js index b61ae2927dc..24a89b62ad2 100644 --- a/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js +++ b/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js @@ -237,4 +237,24 @@ QUnit.module("Fields", (hooks) => { ); } ); + + QUnit.test("BooleanToggleField - auto save record when field toggled", async function (assert) { + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + `, + resId: 1, + mockRPC(_route, { method }) { + if (method === "write") { + assert.step("write"); + } + }, + }); + await click(target, ".o_field_widget[name='bar'] input"); + assert.verifySteps(["write"]); + }); }); diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index 32792dbeca9..2807c4ca778 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -12392,7 +12392,7 @@ QUnit.module("Fields", (hooks) => { assert.verifySteps(["get_views partner", "read partner", "read turtle"]); await click(target, ".o_boolean_toggle"); - assert.verifySteps(["onchange turtle", "onchange partner"]); + assert.verifySteps(["onchange turtle", "onchange partner", "write turtle", "read turtle"]); }); QUnit.test("create a new record with an x2m invisible", async function (assert) { diff --git a/addons/web/static/tests/views/fields/priority_field_tests.js b/addons/web/static/tests/views/fields/priority_field_tests.js index 432b7ebef59..b1586067b47 100644 --- a/addons/web/static/tests/views/fields/priority_field_tests.js +++ b/addons/web/static/tests/views/fields/priority_field_tests.js @@ -611,4 +611,32 @@ QUnit.module("Fields", (hooks) => { assert.containsN(target, "a.fa-star", 2); } ); + + QUnit.test("PriorityField - auto save record when field toggled", async function (assert) { + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData, + arch: ` +
+ + + + + +
`, + mockRPC(_route, { method }) { + if (method === "write") { + assert.step("write"); + } + }, + }); + + const stars = target.querySelectorAll( + ".o_field_widget .o_priority a.o_priority_star.fa-star-o" + ); + await click(stars[stars.length - 1]); + assert.verifySteps(["write"]); + }); }); diff --git a/addons/web/static/tests/views/fields/state_selection_field_tests.js b/addons/web/static/tests/views/fields/state_selection_field_tests.js index ccf2f7b44d9..06455545e6d 100644 --- a/addons/web/static/tests/views/fields/state_selection_field_tests.js +++ b/addons/web/static/tests/views/fields/state_selection_field_tests.js @@ -541,4 +541,33 @@ QUnit.module("Fields", (hooks) => { assert.verifySteps(["write"]); assert.hasClass(target.querySelector(".o_field_state_selection span"), "o_status_green"); }); + + QUnit.test( + "StateSelectionField - auto save record when field toggled", + async function (assert) { + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + + + + +
`, + resId: 1, + mockRPC(_route, { method }) { + if (method === "write") { + assert.step("write"); + } + }, + }); + + await click(target, ".o_field_widget.o_field_state_selection .o_status"); + await click(target, ".dropdown-menu .dropdown-item:last-child"); + assert.verifySteps(["write"]); + } + ); }); diff --git a/addons/web/static/tests/views/fields/statusbar_field_tests.js b/addons/web/static/tests/views/fields/statusbar_field_tests.js index a8085bafc59..bb836f0e79e 100644 --- a/addons/web/static/tests/views/fields/statusbar_field_tests.js +++ b/addons/web/static/tests/views/fields/statusbar_field_tests.js @@ -597,4 +597,29 @@ QUnit.module("Fields", (hooks) => { await nextTick(); assert.containsNone(target, ".modal", "command palette should not open"); }); + + QUnit.test("auto save record when field toggled", async function (assert) { + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData, + arch: ` +
+
+ +
+
`, + mockRPC(_route, { method }) { + if (method === "write") { + assert.step("write"); + } + }, + }); + const clickableButtons = target.querySelectorAll( + ".o_statusbar_status button.btn:not(.dropdown-toggle):not(:disabled):not(.o_arrow_button_current)" + ); + await click(clickableButtons[clickableButtons.length - 1]); + assert.verifySteps(["write"]); + }); }); diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index e92707157ea..24208314a4e 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -1700,7 +1700,7 @@ QUnit.module("Views", (hooks) => { }); QUnit.test("quick create record with quick_create_view", async (assert) => { - assert.expect(19); + assert.expect(20); serverData.views["partner,some_view_ref,form"] = "
" + @@ -1773,6 +1773,7 @@ QUnit.module("Views", (hooks) => { "get_views", // form view in quick create "onchange", // quick create "create", // should perform a create to create the record + "read", "onchange", // new quick create "read", // read the created record ]); @@ -2004,7 +2005,7 @@ QUnit.module("Views", (hooks) => { }); QUnit.test("quick create record in grouped on m2o (with quick_create_view)", async (assert) => { - assert.expect(15); + assert.expect(16); serverData.views["partner,some_view_ref,form"] = "" + @@ -2068,6 +2069,7 @@ QUnit.module("Views", (hooks) => { "get_views", // form view in quick create "onchange", // quick create "create", // should perform a create to create the record + "read", "onchange", // reopen the quick create automatically "read", // read the created record ]); diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js index 33698022b2b..fcc062f31cd 100644 --- a/addons/web/static/tests/views/list_view_tests.js +++ b/addons/web/static/tests/views/list_view_tests.js @@ -2559,12 +2559,7 @@ QUnit.module("Views", (hooks) => {
`, mockRPC(route, args) { if (args.method === "write") { - assert.deepEqual(args.args[1], { - o2m: [ - [1, 1, { grosminet: false }], - [4, 2, false], - ], - }); + assert.deepEqual(args.args[1], { grosminet: false }); } }, }); @@ -2575,7 +2570,6 @@ QUnit.module("Views", (hooks) => { assert.hasClass(target.querySelector(".o_data_row"), "o_selected_row"); assert.containsOnce(target.querySelectorAll(".o_data_cell")[0], ".o_readonly_modifier"); await click(target.querySelectorAll(".o_data_cell")[1], ".o_boolean_toggle input"); - await clickSave(target); }); QUnit.test( @@ -2654,12 +2648,6 @@ QUnit.module("Views", (hooks) => { await click(target.querySelector(".o_data_row .o_data_cell .o_field_boolean_toggle div")); assert.containsOnce(target, ".o_selected_row"); await click(target.querySelector(".o_selected_row .o_field_boolean_toggle div")); - assert.containsOnce(target, ".o_selected_row"); - assert.verifySteps([]); - - // save - await clickSave(target); - assert.containsNone(target, ".o_selected_row"); assert.verifySteps(["write: true"]); });