diff --git a/addons/point_of_sale/static/src/app/models/related_models/base.js b/addons/point_of_sale/static/src/app/models/related_models/base.js index 45da7b1eca5ad..12d597eef0558 100644 --- a/addons/point_of_sale/static/src/app/models/related_models/base.js +++ b/addons/point_of_sale/static/src/app/models/related_models/base.js @@ -29,7 +29,9 @@ export class Base extends WithLazyGetterTrap { * This method is called when the instance is created or updated * @param {*} _vals */ - setup(_vals) {} + setup(_vals) { + this._dirty = typeof this.id !== "number"; + } /** * This method is invoked only during instance creation to preserve the state across updates. @@ -77,7 +79,28 @@ export class Base extends WithLazyGetterTrap { return { ...this.uiState }; } + _markDirty() { + if (this.models._loadingData || this._dirty) { + return; + } + + this._dirty = true; + this.model.getParentFields().forEach((field) => { + this[field.name]?._markDirty?.(); + }); + } + backLink(link) { return this.model.backLink(this, link); } + + getId() { + // Returns integer id if exist or return uuid + const id = this.id; + if (typeof id === "number" || !this.uuid) { + return id; + } + const idUpdates = JSON.parse(localStorage.getItem("idUpdates")) || {}; + return idUpdates[this.uuid] || this.uuid; + } } diff --git a/addons/point_of_sale/static/src/app/models/related_models/index.js b/addons/point_of_sale/static/src/app/models/related_models/index.js index 705b6496f774a..a2a682eae47fc 100644 --- a/addons/point_of_sale/static/src/app/models/related_models/index.js +++ b/addons/point_of_sale/static/src/app/models/related_models/index.js @@ -99,9 +99,29 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) { return result; } + getUuidFromId(id) { + if (typeof id === "string") { + return id; + } + const modelUuids = this.map((rec) => rec.uuid); + const idUpdates = JSON.parse(localStorage.getItem("idUpdates")) || {}; + const entry = Object.entries(idUpdates).find( + ([_uuid, _id]) => modelUuids.includes(_uuid) && _id === id + ); + if (entry) { + return entry[0]; + } + return false; + } + read(value) { const id = /^\d+$/.test(value) ? parseInt(value) : value; // In case of ID came from an input - return this[STORE_SYMBOL].getById(this.name, id); + const record = this[STORE_SYMBOL].getById(this.name, id); + const uuid = record || this.fields["uuid"] ? this.getUuidFromId(id) : null; + if (record || !uuid) { + return record; + } + return this[STORE_SYMBOL].get(this.name, "uuid", uuid); } readFirst() { @@ -450,7 +470,7 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) { } _delete(record, opts = {}) { - const id = record.id; + const id = record.getId(); const ownFields = getFields(this.name); const handleCommand = (inverse, field, record, backend = false) => { if (inverse && !inverse.dummy && typeof id === "number") { @@ -459,7 +479,7 @@ export function createRelatedModels(modelDefs, modelClasses = {}, opts = {}) { const oldVal = map.get(inverse.name); map.set(inverse.name, [ ...(oldVal || []), - { id: record.id, parentId: record[field.name].id }, + { id: record.id, parentId: record[field.name].id, getId: record.getId() }, ]); } }; diff --git a/addons/point_of_sale/static/src/app/models/related_models/serialization.js b/addons/point_of_sale/static/src/app/models/related_models/serialization.js index 081a8c7cffcb3..aa6185d98decd 100644 --- a/addons/point_of_sale/static/src/app/models/related_models/serialization.js +++ b/addons/point_of_sale/static/src/app/models/related_models/serialization.js @@ -53,8 +53,11 @@ const deepSerialization = ( continue; } - if (typeof childRecord.id !== "number") { + if (typeof childRecord.getId() !== "number") { toCreate.push(childRecord); + } else if (childRecord._dirty) { + toUpdate.push(childRecord); + childRecord._dirty = false; } serialized[relatedModel][childRecord.uuid] = childRecord.uuid; } @@ -67,7 +70,7 @@ const deepSerialization = ( ...(result[fieldName] || []), ...toUpdate.map((childRecord) => [ 1, - childRecord.id, + childRecord.getId(), recursiveSerialize(childRecord, field.inverse_name), ]), ...toCreate.map((childRecord) => [ @@ -93,7 +96,7 @@ const deepSerialization = ( if (modelCommands.unlink.has(fieldName) || modelCommands.delete.has(fieldName)) { result[fieldName] = result[fieldName] || []; const processRecords = (records, cmdCode) => { - for (const { id, parentId } of records) { + for (const { id, parentId, getId } of records) { const isAlreadyDeleted = serialized[relatedModel]?.["_deleted_" + id]; if (parentId === record.id && !isAlreadyDeleted) { const isCascadeDelete = @@ -101,7 +104,7 @@ const deepSerialization = ( if (isCascadeDelete) { serialized[relatedModel]["_deleted_" + id] = true; } - result[fieldName].push([cmdCode, id]); + result[fieldName].push([cmdCode, getId]); } } }; @@ -164,6 +167,8 @@ const deepSerialization = ( res[key] = getValue(); } + record._dirty = false; + // Cleanup: remove empty entries from uuidMapping. for (const key in uuidMapping) { if ( diff --git a/addons/point_of_sale/static/src/app/models/related_models/utils.js b/addons/point_of_sale/static/src/app/models/related_models/utils.js index 6d3b5665acd08..87c787bed71cc 100644 --- a/addons/point_of_sale/static/src/app/models/related_models/utils.js +++ b/addons/point_of_sale/static/src/app/models/related_models/utils.js @@ -119,6 +119,7 @@ export class AggregatedUpdates { [...fields].map((fieldName) => [fieldName, record[fieldName]]) ), }); + record._markDirty(); } } diff --git a/addons/point_of_sale/static/tests/unit/related_models/orm_serialization.test.js b/addons/point_of_sale/static/tests/unit/related_models/orm_serialization.test.js index 936a8245fac49..8c71e9de0fd90 100644 --- a/addons/point_of_sale/static/tests/unit/related_models/orm_serialization.test.js +++ b/addons/point_of_sale/static/tests/unit/related_models/orm_serialization.test.js @@ -161,31 +161,31 @@ describe("ORM serialization", () => { } // Server results with ids - models.connectNewData({ - "pos.order": [{ ...order.raw, id: 1, lines: [11, 12] }], - "pos.order.line": [ - { ...line1.raw, id: "b", order_id: 1 }, - { ...line2.raw, id: "c", order_id: 1 }, - ], - }); + // debugger + const idUpdates = { + [order.uuid]: 1, + [line1.uuid]: 11, + [line2.uuid]: 12, + }; + localStorage.setItem("idUpdates", JSON.stringify(idUpdates)); line1.quantity = 99; - // { - // const result = models.serializeForORM(order); - // expect(result.lines.length).toBe(1); - // expect(result.lines[0][0]).toBe(1); - // expect(result.lines[0][1]).toBe(11); - // expect(result.lines[0][2].quantity).toBe(99); - // } - - // // Delete line - // line1.delete(); - // { - // const result = models.serializeForORM(order); - // expect(result.lines.length).toBe(1); - // expect(result.lines[0][0]).toBe(3); - // expect(result.lines[0][1]).toBe(11); - // } + { + const result = models.serializeForORM(order); + expect(result.lines.length).toBe(1); + expect(result.lines[0][0]).toBe(1); + expect(result.lines[0][1]).toBe(11); + expect(result.lines[0][2].quantity).toBe(99); + } + + // Delete line + line1.delete(); + { + const result = models.serializeForORM(order); + expect(result.lines.length).toBe(1); + expect(result.lines[0][0]).toBe(3); + expect(result.lines[0][1]).toBe(11); + } }); test("serialization of non-dynamic model relationships", () => { @@ -287,14 +287,14 @@ describe("ORM serialization", () => { parentLine = models["pos.order.line"].getBy("uuid", parentLine.uuid); line1.quantity = 99; - // { - // const result = models.serializeForORM(order); - // expect(result.lines.length).toBe(1); - // expect(result.lines[0][0]).toBe(1); - // expect(result.lines[0][1]).toBe(11); - // expect(result.lines[0][2].quantity).toBe(99); - // expect(result.relations_uuid_mapping).toBe(undefined); - // } + { + const result = models.serializeForORM(order); + expect(result.lines.length).toBe(1); + expect(result.lines[0][0]).toBe(1); + expect(result.lines[0][1]).toBe(11); + expect(result.lines[0][2].quantity).toBe(99); + expect(result.relations_uuid_mapping).toBe(undefined); + } }); test("recursive relationship with group of lines", () => { @@ -402,46 +402,46 @@ describe("ORM serialization", () => { expect(order.groups[0].lines.length).toBe(1); expect(order.groups[1].lines.length).toBe(1); - // { - // const result = models.serializeForORM(order); - // expect(result.groups[0].lines).toBeEmpty(); - // expect(result.groups[1].lines).toBeEmpty(); - // expect(result.lines.length).toBe(1); - // expect(result.lines[0][0]).toBe(1); - // expect(result.lines[0][1]).toBe(111); - // expect(result.lines[0][2].group_id).toBe(group2.id); - // expect(result.relations_uuid_mapping).toBe(undefined); - // } - - // // Delete line - // line1.delete(); - // //update the group, to be sure the lines are empty - // group1.index = 3; - // group2.index = 4; - // { - // const result = models.serializeForORM(order); - // expect(result.groups.length).toBe(2); - // expect(result.groups[0][0]).toBe(1); - // expect(result.groups[0][1]).toBe(group1.id); - // expect(result.groups[0][2].index).toBe(3); - // expect(result.groups[0][2].lines).toBeEmpty(); - // expect(result.groups[1][0]).toBe(1); - // expect(result.groups[1][1]).toBe(group2.id); - // expect(result.groups[1][2].index).toBe(4); - // expect(result.groups[1][2].lines).toBeEmpty(); - - // expect(result.lines.length).toBe(1); - // expect(result.lines[0][0]).toBe(3); - // expect(result.lines[0][1]).toBe(110); - // expect(result.relations_uuid_mapping).toBe(undefined); - // } - - // { - // //All update/delete have been cleared - // const result = models.serializeForORM(order); - // expect(result.groups).toBeEmpty(); - // expect(result.lines).toBeEmpty(); - // } + { + const result = models.serializeForORM(order); + expect(result.groups[0].lines).toBeEmpty(); + expect(result.groups[1].lines).toBeEmpty(); + expect(result.lines.length).toBe(1); + expect(result.lines[0][0]).toBe(1); + expect(result.lines[0][1]).toBe(111); + expect(result.lines[0][2].group_id).toBe(group2.id); + expect(result.relations_uuid_mapping).toBe(undefined); + } + + // Delete line + line1.delete(); + //update the group, to be sure the lines are empty + group1.index = 3; + group2.index = 4; + { + const result = models.serializeForORM(order); + expect(result.groups.length).toBe(2); + expect(result.groups[0][0]).toBe(1); + expect(result.groups[0][1]).toBe(group1.id); + expect(result.groups[0][2].index).toBe(3); + expect(result.groups[0][2].lines).toBeEmpty(); + expect(result.groups[1][0]).toBe(1); + expect(result.groups[1][1]).toBe(group2.id); + expect(result.groups[1][2].index).toBe(4); + expect(result.groups[1][2].lines).toBeEmpty(); + + expect(result.lines.length).toBe(1); + expect(result.lines[0][0]).toBe(3); + expect(result.lines[0][1]).toBe(110); + expect(result.relations_uuid_mapping).toBe(undefined); + } + + { + //All update/delete have been cleared + const result = models.serializeForORM(order); + expect(result.groups).toBeEmpty(); + expect(result.lines).toBeEmpty(); + } }); test("grouped lines and nested lines", () => { diff --git a/addons/point_of_sale/static/tests/unit/related_models/related_models.test.js b/addons/point_of_sale/static/tests/unit/related_models/related_models.test.js index 8d4f5c60ca55f..4308f846e2282 100644 --- a/addons/point_of_sale/static/tests/unit/related_models/related_models.test.js +++ b/addons/point_of_sale/static/tests/unit/related_models/related_models.test.js @@ -669,7 +669,6 @@ describe("Related Model", () => { order.sampleData = "test"; expect(order.lines.length).toBe(1); - const oldLineId = order.lines[0].id; expect(typeof oldOrderId).toBe("string"); expect(models["pos.order"].get(oldOrderId)).not.toBeEmpty(); @@ -694,11 +693,9 @@ describe("Related Model", () => { expect(order.lines[0].name).toBe("Line 1111"); //Find by ids, uuids - expect(models["pos.order"].get(oldOrderId)).toBe(undefined); expect(models["pos.order"].get(order.id)).toBe(order); expect(models["pos.order"].getBy("uuid", order.uuid)).toBe(order); - expect(models["pos.order.line"].get(oldLineId)).toBe(undefined); expect(models["pos.order.line"].get(line.id)).toBe(line); expect(models["pos.order.line"].getBy("uuid", line.uuid)).toBe(line); });