refactor: add default fallback order object to OrderDisplay to prevent errors when props are missing

This commit is contained in:
Suherdy Yacob 2026-06-03 14:40:03 +07:00
parent 0dcd17039f
commit 85aa55dda9
4 changed files with 82 additions and 0 deletions

View File

@ -24,6 +24,7 @@ Features
'assets': {
'point_of_sale._assets_pos': [
# Incremental rendering patches (existing)
'pos_ui_optimization/static/src/app/services/pos_store_patch.js',
'pos_ui_optimization/static/src/app/components/order_display/order_display_patch.js',
'pos_ui_optimization/static/src/app/components/order_display/order_display_patch.xml',
'pos_ui_optimization/static/src/app/screens/product_screen/product_screen_patch.js',

View File

@ -4,7 +4,23 @@ import { OrderDisplay } from "@point_of_sale/app/components/order_display/order_
import { patch } from "@web/core/utils/patch";
import { useState, useEffect } from "@odoo/owl";
OrderDisplay.props.order = { type: Object, optional: true };
patch(OrderDisplay.prototype, {
get order() {
return this.props.order || {
lines: [],
prices: { taxDetails: { has_tax_groups: false } },
currencyDisplayPriceExcl: "",
currencyAmountTaxes: "",
currencyDisplayPriceIncl: "",
general_customer_note: "",
internal_note: "",
config_id: {},
currency: { id: null },
};
},
setup() {
super.setup();
// Use Object.assign to avoid overwriting state from other patches (e.g. pos_urban_piper)

View File

@ -0,0 +1,63 @@
/** @odoo-module **/
import { PosStore } from "@point_of_sale/app/services/pos_store";
import { patch } from "@web/core/utils/patch";
patch(PosStore.prototype, {
get productToDisplayByCateg() {
// We override this getter to remove Odoo's hardcoded 100-product category limit.
// Because our custom UI optimization module uses progressive rendering (lazy loading)
// on scroll, we can safely return the entire category's product list without risking
// browser freeze or UI lockups.
const sortedProducts = this.productsToDisplay;
if (!this.config.iface_group_by_categ) {
return sortedProducts.length ? [["0", sortedProducts]] : [];
}
const results = [];
const searchWord = this.searchProductWord.trim();
const byCateg = this.models["product.template"].toRaw().getAllBy("pos_categ_ids");
const selectedCategoryIds = !this.selectedCategory
? this.models["pos.category"].map((c) => c.id)
: this.selectedCategory.getAllChildren().map((c) => c.id);
// Sorting in place the categories according to their sequence in the database
selectedCategoryIds.sort((a, b) => {
const categA = this.models["pos.category"].get(a);
const categB = this.models["pos.category"].get(b);
// All category with a parent will be at the end
if (categA.parent_id && !categB.parent_id) {
return 1;
} else if (!categA.parent_id && categB.parent_id) {
return -1;
}
return categA.sequence - categB.sequence;
});
if (!this.selectedCategory) {
// In case of no category selected, we want to display products without category in
// a "Without category" category at the end of the list.
const productWithoutCategory = sortedProducts.filter((p) => !p.pos_categ_ids.length);
byCateg["0"] = productWithoutCategory;
selectedCategoryIds.push("0");
}
for (const catId of selectedCategoryIds) {
const products = byCateg[catId] || [];
const filtered = searchWord
? this.getProductsBySearchWord(searchWord, products)
: products;
if (filtered.length) {
const sorted = this.orderProductBySequenceAndFav(filtered);
// Return the complete list instead of splicing to 100 items.
// Our lazy loading mechanism will handle rendering chunk-by-chunk.
results.push([catId, sorted]);
}
}
return results;
}
});

View File

@ -99,6 +99,8 @@
flex-grow: 1 !important;
flex-shrink: 1 !important;
min-height: 0 !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch;
}
}