diff --git a/addons/point_of_sale/static/src/app/models/lazy_computed.js b/addons/point_of_sale/static/src/app/models/lazy_computed.js new file mode 100644 index 0000000000000..dde2a4cfff7b2 --- /dev/null +++ b/addons/point_of_sale/static/src/app/models/lazy_computed.js @@ -0,0 +1,82 @@ +/** @odoo-module */ + +import { effect } from "@web/core/utils/reactive"; + +function lazyComputed(obj, propName, compute) { + const key = Symbol(propName); + Object.defineProperty(obj, propName, { + get() { + return this[key](); + }, + configurable: true, + }); + + effect( + function recompute(obj) { + const value = []; + obj[key] = () => { + if (!value.length) { + value.push(compute(obj)); + } + return value[0]; + }; + }, + [obj] + ); +} + +function getAllGetters(proto) { + const getterNames = new Set(); + const getters = new Set(); + while (proto !== null) { + const descriptors = Object.getOwnPropertyDescriptors(proto); + for (const [name, descriptor] of Object.entries(descriptors)) { + if (descriptor.get && !getterNames.has(name)) { + getterNames.add(name); + getters.add([name, descriptor.get]); + } + } + proto = Object.getPrototypeOf(proto); + } + return getters; +} + +export function createModelWithLazyGetters(Class) { + const getters = new Map(); + let gettersRegistered = false; + /** + * Getters are registered to the auto-caching mechanism of the lazyComputed. + * To use the auto-caching, a getter must be accessed using the `get` method. + */ + function registerGetters() { + for (const [name, func] of getAllGetters(WithLazyGetters.prototype)) { + if (name.startsWith("__") && name.endsWith("__")) { + continue; + } + const lazyName = `__lazy_${name}`; + getters.set(name, [lazyName, (obj) => func.call(obj)]); + } + gettersRegistered = true; + } + class WithLazyGetters extends Class { + constructor(...args) { + // before creating the first instance, we register the getters + if (!gettersRegistered) { + registerGetters(); + } + super(...args); + for (const [lazyName, func] of getters.values()) { + lazyComputed(this, lazyName, func); + } + } + get(getterName) { + const [lazyName] = getters.get(getterName); + if (lazyName) { + return this[lazyName]; + } else { + throw new Error(`Getter ${getterName} is not defined.`); + } + } + } + return WithLazyGetters; +} diff --git a/addons/point_of_sale/static/src/app/screens/receipt_screen/receipt_screen.js b/addons/point_of_sale/static/src/app/screens/receipt_screen/receipt_screen.js index 3385055c194ac..1f51dd0c92f1c 100644 --- a/addons/point_of_sale/static/src/app/screens/receipt_screen/receipt_screen.js +++ b/addons/point_of_sale/static/src/app/screens/receipt_screen/receipt_screen.js @@ -84,7 +84,7 @@ export class ReceiptScreen extends Component { const tipLine = order .get_orderlines() .find((line) => tip_product_id && line.product.id === tip_product_id); - const tipAmount = tipLine ? tipLine.get_all_prices().priceWithTax : 0; + const tipAmount = tipLine ? tipLine.get("allPrices").priceWithTax : 0; const orderAmountStr = this.env.utils.formatCurrency(orderTotalAmount - tipAmount); if (!tipAmount) { return orderAmountStr; diff --git a/addons/point_of_sale/static/src/app/store/models.js b/addons/point_of_sale/static/src/app/store/models.js index 5ed58fffc1d8f..5c8e4feca6c3d 100644 --- a/addons/point_of_sale/static/src/app/store/models.js +++ b/addons/point_of_sale/static/src/app/store/models.js @@ -23,6 +23,7 @@ import { renderToElement } from "@web/core/utils/render"; import { ProductCustomAttribute } from "./models/product_custom_attribute"; import { omit } from "@web/core/utils/objects"; import { ask } from "@point_of_sale/app/store/make_awaitable_dialog"; +import { createModelWithLazyGetters } from "../models/lazy_computed"; const { DateTime } = luxon; @@ -66,7 +67,7 @@ var orderline_id = 1; // An orderline represent one element of the content of a customer's shopping cart. // An orderline contains a product, its quantity, its price, discount. etc. // An Order contains zero or more Orderlines. -export class Orderline extends PosModel { +class Orderline_Original extends PosModel { setup(_defaultObj, options) { super.setup(...arguments); this.pos = options.pos; @@ -581,9 +582,9 @@ export class Orderline extends PosModel { } get_unit_display_price() { if (this.pos.config.iface_tax_included === "total") { - return this.get_all_prices(1).priceWithTax; + return this.get("allPricesUnit").priceWithTax; } else { - return this.get_all_prices(1).priceWithoutTax; + return this.get("allPricesUnit").priceWithoutTax; } } /** @@ -600,9 +601,9 @@ export class Orderline extends PosModel { } getUnitDisplayPriceBeforeDiscount() { if (this.pos.config.iface_tax_included === "total") { - return this.get_all_prices(1).priceWithTaxBeforeDiscount; + return this.get("allPricesUnit").priceWithTaxBeforeDiscount; } else { - return this.get_all_prices(1).priceWithoutTaxBeforeDiscount; + return this.get("allPricesUnit").priceWithoutTaxBeforeDiscount; } } get_base_price() { @@ -639,16 +640,16 @@ export class Orderline extends PosModel { } } get_price_without_tax() { - return this.get_all_prices().priceWithoutTax; + return this.get("allPrices").priceWithoutTax; } get_price_with_tax() { - return this.get_all_prices().priceWithTax; + return this.get("allPrices").priceWithTax; } get_tax() { - return this.get_all_prices().tax; + return this.get("allPrices").tax; } get_tax_details() { - return this.get_all_prices().taxDetails; + return this.get("allPrices").taxDetails; } get_taxes() { const taxes_ids = @@ -724,6 +725,12 @@ export class Orderline extends PosModel { taxValuesList: taxesData.taxes_data, }; } + get allPrices() { + return this.get_all_prices(); + } + get allPricesUnit() { + return this.get_all_prices(1); + } display_discount_policy() { return this.order.pricelist ? this.order.pricelist.discount_policy : "with_discount"; } @@ -828,6 +835,8 @@ export class Orderline extends PosModel { } } +export const Orderline = createModelWithLazyGetters(Orderline_Original); + export class Packlotline extends PosModel { setup(_defaultObj, options) { super.setup(...arguments); diff --git a/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js b/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js index ae11f1e8eda1d..ccb7c5428ba33 100644 --- a/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js +++ b/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js @@ -19,7 +19,7 @@ export class SplitBillScreen extends Component { setup() { this.pos = usePos(); this.splitlines = useState(this._initSplitLines(this.pos.get_order())); - this.newOrderLines = {}; + this.newOrderLines = useState({}); this.newOrder = undefined; this._isFinal = false; this.newOrder = new Order(