feat: implement robust note splitting and defensive JSON parsing for POS order lines and customer display

This commit is contained in:
Suherdy Yacob 2026-06-02 13:35:49 +07:00
parent 35f254ddb0
commit 19f189dab7
5 changed files with 106 additions and 2 deletions

View File

@ -39,9 +39,14 @@ Features
# ProductScreen portrait tab state + layout # ProductScreen portrait tab state + layout
'pos_ui_optimization/static/src/app/screens/product_screen/portrait_mode_patch.js', 'pos_ui_optimization/static/src/app/screens/product_screen/portrait_mode_patch.js',
'pos_ui_optimization/static/src/app/screens/product_screen/portrait_screen.xml', 'pos_ui_optimization/static/src/app/screens/product_screen/portrait_screen.xml',
# Note splitting and defensive parsing patches
'pos_ui_optimization/static/src/app/screens/product_screen/notes_patch.js',
# Portrait CSS (must load after all XML patches) # Portrait CSS (must load after all XML patches)
'pos_ui_optimization/static/src/scss/portrait.scss', 'pos_ui_optimization/static/src/scss/portrait.scss',
], ],
'point_of_sale.customer_display_assets': [
'pos_ui_optimization/static/src/customer_display/customer_display_patch.js',
],
}, },
'installable': True, 'installable': True,
'application': False, 'application': False,

View File

@ -0,0 +1,84 @@
/** @odoo-module **/
import { NoteButton, InternalNoteButton } from "@point_of_sale/app/screens/product_screen/control_buttons/orderline_note_button/orderline_note_button";
import { Orderline } from "@point_of_sale/app/components/orderline/orderline";
import { patch } from "@web/core/utils/patch";
// 1. Patch NoteButton to fix line-splitting bug for customer notes in standard Odoo
patch(NoteButton.prototype, {
async setChanges(selectedOrderline, payload) {
var quantity_with_note = 0;
const changes = this.pos.getOrderChanges();
for (const key in changes.orderlines) {
if (changes.orderlines[key].uuid === selectedOrderline.uuid) {
quantity_with_note = changes.orderlines[key].quantity;
break;
}
}
const saved_quantity = selectedOrderline.qty - quantity_with_note;
if (saved_quantity > 0 && quantity_with_note > 0) {
const addLineParams = {
product_tmpl_id: selectedOrderline.product_id.product_tmpl_id,
qty: quantity_with_note,
};
if (this.type === "internal") {
addLineParams.note = payload;
} else {
addLineParams.customer_note = payload;
}
await this.pos.addLineToCurrentOrder(addLineParams);
selectedOrderline.qty = saved_quantity;
for (const line of selectedOrderline.combo_line_ids) {
line.setQuantity(line.uiState.oldQty);
}
} else {
this.setOrderlineNote(payload);
}
}
});
// 2. Patch InternalNoteButton to handle invalid JSON gracefully
patch(InternalNoteButton.prototype, {
async onClick() {
const selectedOrderline = this.pos.getOrder().getSelectedOrderline();
let selectedNote = [];
try {
selectedNote = JSON.parse(this.currentNote || "[]");
} catch (e) {
if (this.currentNote) {
selectedNote = [{ text: this.currentNote, colorIndex: 0 }];
}
}
const payload = await this.openTextInput(selectedNote.map((n) => n.text).join("\n"));
const coloredNotes = payload ? this.reframeNotes(payload) : "[]";
if (selectedOrderline) {
this.setChanges(selectedOrderline, coloredNotes);
} else {
this.pos.getOrder().setInternalNote(coloredNotes);
}
return {
confirmed: typeof payload === "string",
inputNote: coloredNotes,
oldNote: JSON.stringify(selectedNote),
};
}
});
// 3. Patch Orderline to prevent UI crashes if note contains invalid JSON
patch(Orderline.prototype, {
get lineScreenValues() {
const originalParse = JSON.parse;
JSON.parse = function (str) {
try {
return originalParse(str);
} catch (e) {
return [{ text: str, colorIndex: 0 }];
}
};
try {
return super.lineScreenValues;
} finally {
JSON.parse = originalParse;
}
}
});

View File

@ -1,7 +1,7 @@
/** @odoo-module **/ /** @odoo-module **/
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
import { NoteButton } from "@point_of_sale/app/screens/product_screen/control_buttons/orderline_note_button/orderline_note_button"; import { NoteButton, InternalNoteButton } from "@point_of_sale/app/screens/product_screen/control_buttons/orderline_note_button/orderline_note_button";
import { patch } from "@web/core/utils/patch"; import { patch } from "@web/core/utils/patch";
import { useState } from "@odoo/owl"; import { useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
@ -32,4 +32,5 @@ patch(ProductScreen.prototype, {
ProductScreen.components = { ProductScreen.components = {
...ProductScreen.components, ...ProductScreen.components,
NoteButton, NoteButton,
InternalNoteButton,
}; };

View File

@ -43,7 +43,7 @@
<xpath expr="//Numpad" position="before"> <xpath expr="//Numpad" position="before">
<div t-if="portraitMode.isPortrait and !currentOrder.isEmpty() and pos.getOrder()?.uiState.selected_orderline_uuid" <div t-if="portraitMode.isPortrait and !currentOrder.isEmpty() and pos.getOrder()?.uiState.selected_orderline_uuid"
class="portrait-numpad-actions d-flex gap-2 mb-2 w-100"> class="portrait-numpad-actions d-flex gap-2 mb-2 w-100">
<NoteButton label="'Note'" class="'btn btn-outline-primary flex-fill d-flex align-items-center justify-content-center gap-2 py-3 fs-5 fw-bold rounded-3'" /> <InternalNoteButton label="'Note'" class="'btn btn-outline-primary flex-fill d-flex align-items-center justify-content-center gap-2 py-3 fs-5 fw-bold rounded-3'" />
<button class="btn btn-outline-danger flex-fill d-flex align-items-center justify-content-center gap-2 py-3 fs-5 fw-bold rounded-3" <button class="btn btn-outline-danger flex-fill d-flex align-items-center justify-content-center gap-2 py-3 fs-5 fw-bold rounded-3"
t-on-click="onDeleteLine"> t-on-click="onDeleteLine">
<i class="fa fa-trash-o fa-lg"/>Delete <i class="fa fa-trash-o fa-lg"/>Delete

View File

@ -0,0 +1,14 @@
/** @odoo-module **/
import { CustomerDisplay } from "@point_of_sale/customer_display/customer_display";
import { patch } from "@web/core/utils/patch";
patch(CustomerDisplay.prototype, {
getInternalNotes() {
try {
return JSON.parse(this.line.internalNote || "[]");
} catch (e) {
return [{ text: this.line.internalNote, colorIndex: 0 }];
}
}
});