[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) <aab@odoo.com>
Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
Joseph Caburnay
2023-03-16 13:07:57 +01:00
parent ff283b6b1a
commit e1c088a8ce
13 changed files with 123 additions and 64 deletions
@@ -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.']);
});
@@ -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();
}
}
@@ -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();
}
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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) {
@@ -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: `
<form>
<field name="bar" widget="boolean_toggle" />
</form>`,
resId: 1,
mockRPC(_route, { method }) {
if (method === "write") {
assert.step("write");
}
},
});
await click(target, ".o_field_widget[name='bar'] input");
assert.verifySteps(["write"]);
});
});
@@ -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) {
@@ -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: `
<form>
<sheet>
<group>
<field name="selection" widget="priority" />
</group>
</sheet>
</form>`,
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"]);
});
});
@@ -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: `
<form>
<sheet>
<group>
<field name="selection" widget="state_selection"/>
</group>
</sheet>
</form>`,
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"]);
}
);
});
@@ -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: `
<form>
<header>
<field name="trululu" widget="statusbar" options="{'clickable': 1}" />
</header>
</form>`,
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"]);
});
});
@@ -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"] =
"<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"] =
"<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
]);
@@ -2559,12 +2559,7 @@ QUnit.module("Views", (hooks) => {
</form>`,
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"]);
});