[FIX] web: wait for static list operation before saving

Have a required many2manytags field and a slow network. The field
is initially empty. Add a tag to the relation and directly save.
An invalid field notification is displayed, because the field is
still considered empty. This commit ensures that we wait for the
operations done in the static list before saving.

Part of task~3179751

Part-of: odoo/odoo#114024
This commit is contained in:
Aaron Bohy
2023-07-24 20:17:52 +02:00
parent a748e3c4df
commit f6a2086375
2 changed files with 86 additions and 29 deletions
@@ -168,9 +168,11 @@ export class StaticList extends DataPoint {
});
}
async delete(record) {
await this._applyCommands([[x2ManyCommands.DELETE, record.resId || record._virtualId]]);
this._onUpdate();
delete(record) {
return this.model.mutex.exec(async () => {
await this._applyCommands([[x2ManyCommands.DELETE, record.resId || record._virtualId]]);
await this._onUpdate();
});
}
async enterEditMode(record) {
@@ -279,9 +281,11 @@ export class StaticList extends DataPoint {
});
}
async forget(record) {
await this._applyCommands([[x2ManyCommands.FORGET, record.resId]]);
this._onUpdate();
forget(record) {
return this.model.mutex.exec(async () => {
await this._applyCommands([[x2ManyCommands.FORGET, record.resId]]);
await this._onUpdate();
});
}
leaveEditMode({ discard, canAbandon, validate } = {}) {
@@ -312,9 +316,11 @@ export class StaticList extends DataPoint {
});
}
async linkTo(resId, serverData) {
await this._applyCommands([[x2ManyCommands.LINK_TO, resId, serverData]]);
this._onUpdate();
linkTo(resId, serverData) {
return this.model.mutex.exec(async () => {
await this._applyCommands([[x2ManyCommands.LINK_TO, resId, serverData]]);
await this._onUpdate();
});
}
load({ limit, offset, orderBy } = {}) {
@@ -338,27 +344,29 @@ export class StaticList extends DataPoint {
}
async replaceWith(ids, { reload = false, silent = false } = {}) {
const resIds = reload ? ids : ids.filter((id) => !this._cache[id]);
if (resIds.length) {
const records = await this.model._loadRecords({
...this.config,
resIds,
context: this.context,
});
for (const record of records) {
this._createRecordDatapoint(record);
return this.model.mutex.exec(async () => {
const resIds = reload ? ids : ids.filter((id) => !this._cache[id]);
if (resIds.length) {
const records = await this.model._loadRecords({
...this.config,
resIds,
context: this.context,
});
for (const record of records) {
this._createRecordDatapoint(record);
}
}
}
this.records = ids.map((id) => this._cache[id]);
const updateCommandsToKeep = this._commands.filter(
(c) => c[0] === x2ManyCommands.UPDATE && ids.includes(c[1])
);
this._commands = [x2ManyCommands.replaceWith(ids)].concat(updateCommandsToKeep);
this._currentIds = [...ids];
this.count = this._currentIds.length;
if (!silent) {
this._onUpdate();
}
this.records = ids.map((id) => this._cache[id]);
const updateCommandsToKeep = this._commands.filter(
(c) => c[0] === x2ManyCommands.UPDATE && ids.includes(c[1])
);
this._commands = [x2ManyCommands.replaceWith(ids)].concat(updateCommandsToKeep);
this._currentIds = [...ids];
this.count = this._currentIds.length;
if (!silent) {
await this._onUpdate();
}
});
}
async resequence(movedId, targetId) {
@@ -1758,6 +1758,55 @@ QUnit.module("Fields", (hooks) => {
assert.containsOnce(target, "[name='timmy'].o_field_invalid");
});
QUnit.test("set a required many2many_tags and save directly", async function (assert) {
let def;
const form = await makeView({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="timmy" widget="many2many_tags" required="1"/></form>',
async mockRPC(route, args) {
assert.step(args.method);
if (args.method === "web_read") {
await def;
}
}
});
patchWithCleanup(form.env.services.notification, {
add: () => assert.step("notification"),
});
assert.verifySteps([
"get_views",
"onchange2",
]);
assert.containsNone(target, ".o_tag");
def = makeDeferred();
await clickDropdown(target, "timmy");
await clickOpenedDropdownItem(target, "timmy", "gold");
assert.containsNone(target, ".o_tag");
assert.verifySteps([
"name_search",
"web_read",
]);
await clickSave(target);
assert.doesNotHaveClass(target, "[name='timmy']", "o_field_invalid");
assert.verifySteps([]);
def.resolve();
await nextTick();
assert.verifySteps([
"create",
"web_read",
]);
});
QUnit.test("Many2ManyTagsField with option 'no_quick_create' set to true", async (assert) => {
serverData.views = {
"partner_type,false,form": `<form><field name="name"/><field name="color"/></form>`,