diff --git a/addons/web/static/src/legacy/legacy_fields.js b/addons/web/static/src/legacy/legacy_fields.js index e8ed4f2c42f..13ba37903a4 100644 --- a/addons/web/static/src/legacy/legacy_fields.js +++ b/addons/web/static/src/legacy/legacy_fields.js @@ -336,6 +336,19 @@ function registerField(name, LegacyFieldWidget) { update(fieldName, value) { if (fieldName === this.props.name) { + const record = this.props.record; + const fieldType = record.fields[fieldName].type; + if (["one2many", "many2many"].includes(fieldType) && !record.model.__bm__) { + const staticList = this.props.value; + switch (value.operation) { + case "REPLACE_WITH": { + return staticList.replaceWith(value.resIds); + } + case "FORGET": { + return staticList.delete(value.ids); + } + } + } return this.props.update(value); } else { return this.props.record.update({ [fieldName]: value }); diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index 033c5dc801f..4fc7371a6ab 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -2719,13 +2719,25 @@ export class StaticList extends DataPoint { /** * @param {RecordId} recordId */ - async delete(recordId) { - const record = this._cache[recordId]; - if (record.isVirtual) { - delete this._cache[recordId]; + async delete(recordIds) { + if (!Array.isArray(recordIds)) { + recordIds = [recordIds]; } - const id = record.resId || record.virtualId; - this.applyCommand(x2ManyCommands.delete(id)); + const ids = []; + for (const recordId of recordIds) { + const record = this._cache[recordId]; + if (record.isVirtual) { + delete this._cache[recordId]; + } + const id = record.resId || record.virtualId; + ids.push(id); + } + if (this.field.type === "many2many") { + const nextIds = this.records.filter((rec) => !ids.includes(rec.resId || rec.virtualId)); + return this.replaceWith(nextIds); + } + const commands = ids.map((id) => x2ManyCommands.delete(id)); + this.applyCommands(commands); await this._loadRecords(); this.records = this._getRecords(); this.onChanges(); diff --git a/addons/web/static/tests/views/fields/many2many_field_tests.js b/addons/web/static/tests/views/fields/many2many_field_tests.js index 90f7a1b18d1..42198047f9c 100644 --- a/addons/web/static/tests/views/fields/many2many_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_field_tests.js @@ -20,6 +20,9 @@ import { session } from "@web/session"; import { companyService } from "@web/webclient/company_service"; import { registry } from "@web/core/registry"; +import legacyFieldRegistry from "web.field_registry"; +import { FieldMany2ManyTags } from "web.relational_fields"; + let target; let serverData; @@ -2052,4 +2055,40 @@ QUnit.module("Fields", (hooks) => { ); } ); + + QUnit.test("many2many legacy field in list add a record", async (assert) => { + const myM2M = FieldMany2ManyTags.extend({}); + legacyFieldRegistry.add("many2many_tags_legacy", myM2M); + + await makeView({ + type: "list", + resModel: "partner", + serverData, + arch: ` + + + `, + mockRPC(route, args) { + if (args.method === "write") { + assert.step(`write: ${JSON.stringify(args.args[1])}`); + } + }, + }); + + assert.containsNone(target, ".o_badge_text"); + await click(target.querySelectorAll(".o_data_cell")[0]); + await click(target, ".o_legacy_field_widget input"); + await click(document.querySelectorAll(".ui-autocomplete .dropdown-item")[0]); + + assert.strictEqual(target.querySelector(".o_badge_text").textContent, "gold"); + await click(target); + assert.verifySteps([`write: {"timmy":[[6,false,[12]]]}`]); + + await click(target.querySelectorAll(".o_data_cell")[0]); + await click(target.querySelector(".badge .o_delete")); + await click(target); + assert.containsNone(target, ".o_badge_text"); + assert.verifySteps([`write: {"timmy":[[6,false,[]]]}`]); + delete legacyFieldRegistry.map["many2many_tags_legacy"]; + }); });