From e57719733df65d3f53eee70a1f045ba6361db7ad Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Tue, 10 Jan 2023 08:57:54 +0000 Subject: [PATCH] [REF] point_of_sale, *: move things out of Chrome and store *: pos_hr_restaurant, pos_restaurant This commit continues the work of simplifying the Chrome component, it moves the popup controller and the notification to their own component and service, and moves the state of the notification, sound and debug widget into their own services. closes odoo/odoo#111553 Related: odoo/enterprise#36573 Signed-off-by: Samuel Degueldre --- addons/point_of_sale/__manifest__.py | 1 + .../static/src/app/debug/debug_service.js | 34 +++++++++++ .../{debug_widget => debug}/debug_widget.js | 9 +-- .../{debug_widget => debug}/debug_widget.scss | 0 .../{debug_widget => debug}/debug_widget.xml | 4 +- .../static/src/app/navbar/navbar.js | 2 + .../static/src/app/navbar/navbar.xml | 2 +- .../src/app/notification/notification.scss | 25 ++++++++ .../src/app/notification/notification.xml | 20 ++++++ .../app/notification/notification_service.js | 50 +++++++++++++++ .../point_of_sale/static/src/app/pos_store.js | 26 ++++---- addons/point_of_sale/static/src/js/Chrome.js | 61 ++++++------------- .../static/src/js/Notification.js | 21 ------- .../src/js/Popups/PosPopupController.js | 15 ++++- .../static/src/js/PosComponent.js | 14 +++-- addons/point_of_sale/static/src/scss/pos.scss | 18 ------ addons/point_of_sale/static/src/utils.js | 43 +++++++++++++ .../point_of_sale/static/src/xml/Chrome.xml | 24 +++----- .../static/src/xml/Notification.xml | 12 ---- .../tests/unit/test_PosPopupController.js | 33 +++++++--- .../pos_hr_restaurant/static/src/js/Chrome.js | 6 +- addons/pos_restaurant/static/src/js/Chrome.js | 6 +- 22 files changed, 270 insertions(+), 156 deletions(-) create mode 100644 addons/point_of_sale/static/src/app/debug/debug_service.js rename addons/point_of_sale/static/src/app/{debug_widget => debug}/debug_widget.js (95%) rename addons/point_of_sale/static/src/app/{debug_widget => debug}/debug_widget.scss (100%) rename addons/point_of_sale/static/src/app/{debug_widget => debug}/debug_widget.xml (94%) create mode 100644 addons/point_of_sale/static/src/app/notification/notification.scss create mode 100644 addons/point_of_sale/static/src/app/notification/notification.xml create mode 100644 addons/point_of_sale/static/src/app/notification/notification_service.js delete mode 100644 addons/point_of_sale/static/src/js/Notification.js create mode 100644 addons/point_of_sale/static/src/utils.js delete mode 100644 addons/point_of_sale/static/src/xml/Notification.xml diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index 0e42a7cb1ea..b413c6ca03e 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -125,6 +125,7 @@ 'point_of_sale/static/src/js/**/*.js', 'web/static/lib/zxing-library/zxing-library.js', 'point_of_sale/static/src/xml/**/*.xml', + 'point_of_sale/static/src/utils.js', 'point_of_sale/static/src/app/**/*', ], # This bundle contains the code responsible for starting the POS UI. diff --git a/addons/point_of_sale/static/src/app/debug/debug_service.js b/addons/point_of_sale/static/src/app/debug/debug_service.js new file mode 100644 index 00000000000..1035c35644e --- /dev/null +++ b/addons/point_of_sale/static/src/app/debug/debug_service.js @@ -0,0 +1,34 @@ +/** @odoo-module */ + +import { reactive } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { DebugWidget } from "./debug_widget"; +import { withComputedProperties } from "@point_of_sale/utils"; + +const debugService = { + dependencies: ["pos"], + /** + * @param {{ debug: string }} env + * @param {object} deps + * @param {import("@point_of_sale/app/pos_store").PosStore} deps.pos + */ + start(env, { pos }) { + const internalState = reactive({ widgetOpen: true }); + const state = withComputedProperties(reactive({}), [internalState, pos], { + showWidget(internalState, pos) { + return env.debug && pos.uiState === "READY" && internalState.widgetOpen; + }, + }); + registry.category("main_components").add("DebugWidget", { + Component: DebugWidget, + props: { state }, + }); + return { + toggleWidget() { + internalState.widgetOpen = !internalState.widgetOpen; + }, + }; + }, +}; + +registry.category("services").add("debug", debugService); diff --git a/addons/point_of_sale/static/src/app/debug_widget/debug_widget.js b/addons/point_of_sale/static/src/app/debug/debug_widget.js similarity index 95% rename from addons/point_of_sale/static/src/app/debug_widget/debug_widget.js rename to addons/point_of_sale/static/src/app/debug/debug_widget.js index 378e34bf120..7b39a553da1 100644 --- a/addons/point_of_sale/static/src/app/debug_widget/debug_widget.js +++ b/addons/point_of_sale/static/src/app/debug/debug_widget.js @@ -3,21 +3,20 @@ import { parse } from "web.field_utils"; import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; import { PosComponent } from "@point_of_sale/js/PosComponent"; -import { usePos } from "@point_of_sale/app/pos_store"; -import { registry } from "@web/core/registry"; import { Transition } from "@web/core/transition"; import { Draggable } from "@point_of_sale/js/Misc/Draggable"; import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; import { OrderImportPopup } from "@point_of_sale/js/Popups/OrderImportPopup"; +import { useService } from "@web/core/utils/hooks"; -const { onMounted, onWillUnmount, useRef, useState } = owl; +import { onMounted, onWillUnmount, useRef, useState } from "@odoo/owl"; export class DebugWidget extends PosComponent { static components = { Transition, Draggable }; static template = "point_of_sale.DebugWidget"; setup() { super.setup(); - this.pos = usePos(); + this.debug = useService("debug"); this.state = useState({ barcodeInput: "", weightInput: "", @@ -162,5 +161,3 @@ export class DebugWidget extends PosComponent { return `"${this.state.buffer}"`; } } - -registry.category("main_components").add("DebugWidget", { Component: DebugWidget }); diff --git a/addons/point_of_sale/static/src/app/debug_widget/debug_widget.scss b/addons/point_of_sale/static/src/app/debug/debug_widget.scss similarity index 100% rename from addons/point_of_sale/static/src/app/debug_widget/debug_widget.scss rename to addons/point_of_sale/static/src/app/debug/debug_widget.scss diff --git a/addons/point_of_sale/static/src/app/debug_widget/debug_widget.xml b/addons/point_of_sale/static/src/app/debug/debug_widget.xml similarity index 94% rename from addons/point_of_sale/static/src/app/debug_widget/debug_widget.xml rename to addons/point_of_sale/static/src/app/debug/debug_widget.xml index 4cac0c1568a..f41876ef80f 100644 --- a/addons/point_of_sale/static/src/app/debug_widget/debug_widget.xml +++ b/addons/point_of_sale/static/src/app/debug/debug_widget.xml @@ -2,13 +2,13 @@ - +

Debug Window

-

Electronic Scale

diff --git a/addons/point_of_sale/static/src/app/navbar/navbar.js b/addons/point_of_sale/static/src/app/navbar/navbar.js index a90c56327f6..3ab31a7ad2b 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.js +++ b/addons/point_of_sale/static/src/app/navbar/navbar.js @@ -2,6 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { usePos } from "@point_of_sale/app/pos_store"; +import { useService } from "@web/core/utils/hooks"; import { CashierName } from "@point_of_sale/js/ChromeWidgets/CashierName"; import { CashMoveButton } from "@point_of_sale/js/ChromeWidgets/CashMoveButton"; @@ -30,6 +31,7 @@ export class Navbar extends PosComponent { }; setup() { this.pos = usePos(); + this.debug = useService("debug"); } get customerFacingDisplayButtonIsShown() { return this.env.pos.config.iface_customer_facing_display; diff --git a/addons/point_of_sale/static/src/app/navbar/navbar.xml b/addons/point_of_sale/static/src/app/navbar/navbar.xml index cdf9e7a3d85..397c3de6d5f 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.xml +++ b/addons/point_of_sale/static/src/app/navbar/navbar.xml @@ -5,7 +5,7 @@
- +
diff --git a/addons/point_of_sale/static/src/app/notification/notification.scss b/addons/point_of_sale/static/src/app/notification/notification.scss new file mode 100644 index 00000000000..5f4a172f7dd --- /dev/null +++ b/addons/point_of_sale/static/src/app/notification/notification.scss @@ -0,0 +1,25 @@ +.o_pos_notification_manager { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + padding: 2rem; + justify-content: end; + align-items: center; + pointer-events: none; +} + +.o_notification { + pointer-events: auto; + width: 350px; + background-color: #333333AA; + color: #fff; + text-align: center; + border-radius: 2px; + padding: 16px; + margin: .5rem; + font-size: 1rem; +} diff --git a/addons/point_of_sale/static/src/app/notification/notification.xml b/addons/point_of_sale/static/src/app/notification/notification.xml new file mode 100644 index 00000000000..f76d88f9d6f --- /dev/null +++ b/addons/point_of_sale/static/src/app/notification/notification.xml @@ -0,0 +1,20 @@ + + + + +
+ +
+
+ + +
+ + + + + +
+
+ +
diff --git a/addons/point_of_sale/static/src/app/notification/notification_service.js b/addons/point_of_sale/static/src/app/notification/notification_service.js new file mode 100644 index 00000000000..476ee01d36c --- /dev/null +++ b/addons/point_of_sale/static/src/app/notification/notification_service.js @@ -0,0 +1,50 @@ +/** @odoo-module */ + +import { reactive, Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Transition } from "@web/core/transition"; + +const TRANSITION_LEAVE_DURATION = 200; // ms + +class Notification extends Component { + static template = "point_of_sale.Notification"; + static props = { message: String, close: Function, className: String }; +} + +class NotificationContainer extends Component { + static template = "point_of_sale.NotificationContainer"; + static components = { Transition, Notification }; + static props = { + notifications: Object, + }; + leaveDuration = TRANSITION_LEAVE_DURATION; +} +// FIXME: probably should use the main notification service from web long term. +export const notificationService = { + start() { + const notifications = reactive({}); + let notifId = 0; + registry.category("main_components").add("PosNotificationContainer", { + Component: NotificationContainer, + props: { notifications }, + }); + return { + add(message, duration = 2000) { + const id = ++notifId; + notifications[id] = { + message, + visible: true, + close() { + notifications[id].visible = false; + }, + delete() { + delete notifications[id]; + }, + }; + setTimeout(() => notifications?.[id]?.close(), duration); + }, + }; + }, +}; + +registry.category("services").add("pos_notification", notificationService); diff --git a/addons/point_of_sale/static/src/app/pos_store.js b/addons/point_of_sale/static/src/app/pos_store.js index b961d23c138..13c3f006d8d 100644 --- a/addons/point_of_sale/static/src/app/pos_store.js +++ b/addons/point_of_sale/static/src/app/pos_store.js @@ -1,32 +1,27 @@ /** @odoo-module */ +import { PosGlobalState } from "@point_of_sale/js/models"; +import { pos_env as legacyEnv } from "@point_of_sale/js/pos_env"; + import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; -import { debounce } from "@web/core/utils/timing"; -import { reactive, useState, toRaw } from "@odoo/owl"; +import { reactive, useState, markRaw } from "@odoo/owl"; export class PosStore { /** @type {'LOADING' | 'READY' | 'CLOSING'} */ uiState = "LOADING"; - debugWidgetIsShown = true; hasBigScrollBars = false; - notification = { - isShown: false, - message: "", - duration: 2000, - }; loadingSkipButtonIsShown = false; - // not using an arrow function here because we need the correct `this` - toggleDebugWidget = debounce(function toggleDebugWidget() { - this.debugWidgetIsShown = !toRaw(this).debugWidgetIsShown; - }, 100); mainScreen = { name: null, component: null }; tempScreen = null; + legacyEnv = legacyEnv; constructor() { this.setup(); } - // to allow other modules to add things to the store. - setup() {} + // use setup instead of constructor because setup can be patched. + setup() { + this.globalState = new PosGlobalState({ env: markRaw(legacyEnv) }); + } } export const posService = { @@ -37,6 +32,9 @@ export const posService = { registry.category("services").add("pos", posService); +/** + * @returns {PosStore} + */ export function usePos() { return useState(useService("pos")); } diff --git a/addons/point_of_sale/static/src/js/Chrome.js b/addons/point_of_sale/static/src/js/Chrome.js index dc47b4409e1..846a8714b87 100644 --- a/addons/point_of_sale/static/src/js/Chrome.js +++ b/addons/point_of_sale/static/src/js/Chrome.js @@ -15,13 +15,10 @@ import { Navbar } from "@point_of_sale/app/navbar/navbar"; // ChromeAdapter imports import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { PosGlobalState } from "@point_of_sale/js/models"; import { configureGui } from "@point_of_sale/js/Gui"; import { registry } from "@web/core/registry"; import { pos_env as env } from "@point_of_sale/js/pos_env"; -import { Notification } from "./Notification"; -import { PosPopupController } from "./Popups/PosPopupController"; import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup"; import { CashOpeningPopup } from "./Popups/CashOpeningPopup"; import { ConfirmPopup } from "./Popups/ConfirmPopup"; @@ -32,7 +29,6 @@ import { useExternalListener, useSubEnv, reactive, - markRaw, onWillUnmount, } from "@odoo/owl"; import { usePos } from "@point_of_sale/app/pos_store"; @@ -43,18 +39,15 @@ import { usePos } from "@point_of_sale/app/pos_store"; export class Chrome extends PosComponent { static template = "Chrome"; // FIXME POSREF namespace templates setup() { + this.pos = usePos(); // BEGIN ChromeAdapter ProductScreen.sortControlButtons(); const legacyActionManager = useService("legacy_action_manager"); - // Instantiate PosGlobalState here to ensure that every extension - // (or class overloads) is taken into consideration. - const pos = new PosGlobalState({ env: markRaw(env) }); - this.batchedCustomerDisplayRender = batched(() => { reactivePos.send_current_order_to_customer_facing_display(); }); - const reactivePos = reactive(pos, this.batchedCustomerDisplayRender); + const reactivePos = reactive(this.pos.globalState, this.batchedCustomerDisplayRender); env.pos = reactivePos; env.legacyActionManager = legacyActionManager; @@ -63,12 +56,13 @@ export class Chrome extends PosComponent { // TODO: Should we continue on exposing posmodel as global variable? // Expose only the reactive version of `pos` when in debug mode. - window.posmodel = pos.debug ? reactivePos : pos; + window.posmodel = this.pos.globalState.debug ? reactivePos : this.pos.globalState; this.wowlEnv = this.env; - env.services.pos = this.wowlEnv.services.pos; - env.services.sound = this.wowlEnv.services.sound; - window.sound = env.services.sound; + for (const service of ["pos", "sound", "debug", "pos_notification"]) { + env.services[service] = this.wowlEnv.services[service]; + } + this.env = env; this.__owl__.childEnv = env; useSubEnv({ @@ -94,20 +88,11 @@ export class Chrome extends PosComponent { useListener("close-temp-screen", this.__closeTempScreen); useListener("close-pos", this._closePos); useListener("loading-skip-callback", () => this.env.proxy.stop_searching()); - const sound = useService("sound"); - useListener("play-sound", ({ detail: name }) => sound.play(name)); useListener("set-sync-status", this._onSetSyncStatus); - useListener("show-notification", this._onShowNotification); - useListener("close-notification", this._onCloseNotification); useListener("connect-to-proxy", this.connect_to_proxy); useBus(this.env.posbus, "start-cash-control", this.openCashControl); numberBuffer.activate(); - this.state = usePos(); - - this.mainScreen = this.state.mainScreen; - this.mainScreenProps = {}; - useSubEnv({ pos: reactive( this.env.pos, @@ -145,9 +130,9 @@ export class Chrome extends PosComponent { * NOTE: Wait for pos data to be completed before calling this getter. */ get startScreen() { - if (this.state.uiState !== "READY") { + if (this.pos.uiState !== "READY") { console.warn( - `Accessing startScreen of Chrome component before 'state.uiState' to be 'READY' is not recommended.` + `Accessing startScreen of Chrome component before 'pos.uiState' to be 'READY' is not recommended.` ); } return { name: "ProductScreen" }; @@ -184,7 +169,7 @@ export class Chrome extends PosComponent { this.env.pos.config.start_category && this.env.pos.config.iface_start_categ_id ? this.env.pos.config.iface_start_categ_id[0] : 0; - this.state.uiState = "READY"; + this.pos.uiState = "READY"; this._showStartScreen(); setTimeout(() => this._runBackgroundTasks()); } catch (error) { @@ -267,7 +252,7 @@ export class Chrome extends PosComponent { connect_to_proxy() { return new Promise((resolve, reject) => { this.env.barcode_reader.disconnect_from_proxy(); - this.state.loadingSkipButtonIsShown = true; + this.pos.loadingSkipButtonIsShown = true; this.env.proxy .autoconnect({ force_ip: this.env.pos.config.proxy_ip || undefined, @@ -326,21 +311,19 @@ export class Chrome extends PosComponent { } __showTempScreen(event) { const { name, props, resolve } = event.detail; - this.state.tempScreen = { + this.pos.tempScreen = { name, component: registry.category("pos_screens").get(name), props: { ...props, resolve }, }; } __closeTempScreen() { - this.state.tempScreen = null; + this.pos.tempScreen = null; } __showScreen({ detail: { name, props = {} } }) { const component = registry.category("pos_screens").get(name); // 1. Set the information of the screen to display. - this.mainScreen.name = name; - this.mainScreen.component = component; - this.mainScreenProps = props; + this.pos.mainScreen = { name, component, props }; // 2. Save the screen to the order. // - This screen is shown when the order is selected. @@ -402,8 +385,8 @@ export class Chrome extends PosComponent { }); if (confirmed) { // FIXME POSREF setting the location prevents the next render, the loading screen never shows - this.state.uiState = "CLOSING"; - this.state.loadingSkipButtonIsShown = false; + this.pos.uiState = "CLOSING"; + this.pos.loadingSkipButtonIsShown = false; window.location = "/web#action=point_of_sale.action_client_pos_menu"; } } @@ -413,14 +396,6 @@ export class Chrome extends PosComponent { this.env.pos.synch.status = status; this.env.pos.synch.pending = pending; } - _onShowNotification({ detail: { message, duration } }) { - this.state.notification.isShown = true; - this.state.notification.message = message; - this.state.notification.duration = duration; - } - _onCloseNotification() { - this.state.notification.isShown = false; - } /** * Save `env.pos.toRefundLines` in localStorage on beforeunload - closing the * browser, reloading or going to other page. @@ -458,7 +433,7 @@ export class Chrome extends PosComponent { } if (this.env.pos.config.iface_big_scrollbars) { - this.state.hasBigScrollBars = true; + this.pos.hasBigScrollBars = true; } this._disableBackspaceBack(); @@ -507,8 +482,6 @@ Object.defineProperty(Chrome, "components", { MainComponentsContainer, WithEnv, Navbar, - PosPopupController, - Notification, }, PosComponent.components ); diff --git a/addons/point_of_sale/static/src/js/Notification.js b/addons/point_of_sale/static/src/js/Notification.js deleted file mode 100644 index 9f029b2f40c..00000000000 --- a/addons/point_of_sale/static/src/js/Notification.js +++ /dev/null @@ -1,21 +0,0 @@ -/** @odoo-module */ - -import { useListener } from "@web/core/utils/hooks"; -import { PosComponent } from "@point_of_sale/js/PosComponent"; - -const { onMounted } = owl; - -export class Notification extends PosComponent { - static template = "Notification"; - - setup() { - super.setup(); - useListener("click", this.closeNotification); - - onMounted(() => { - setTimeout(() => { - this.closeNotification(); - }, this.props.duration); - }); - } -} diff --git a/addons/point_of_sale/static/src/js/Popups/PosPopupController.js b/addons/point_of_sale/static/src/js/Popups/PosPopupController.js index 6dc72e4edde..70e76618f5a 100644 --- a/addons/point_of_sale/static/src/js/Popups/PosPopupController.js +++ b/addons/point_of_sale/static/src/js/Popups/PosPopupController.js @@ -2,6 +2,9 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { useBus } from "@web/core/utils/hooks"; +import { registry } from "@web/core/registry"; +import { usePos } from "@point_of_sale/app/pos_store"; +import { useExternalListener, useState } from "@odoo/owl"; /** * This component is responsible in controlling the popups. It does so @@ -29,11 +32,15 @@ export class PosPopupController extends PosComponent { static components = {}; setup() { + this.pos = usePos(); + // FIXME POSREF stop overwriting env + this.env = this.pos.legacyEnv; + this.__owl__.childEnv = this.env; super.setup(); useBus(this.env.posbus, "show-popup", this._showPopup); useBus(this.env.posbus, "close-popup", this._closePopup); - owl.useExternalListener(window, "keyup", this._onWindowKeyup); - this.popups = owl.useState([]); + useExternalListener(window, "keyup", this._onWindowKeyup); + this.popups = useState([]); } /** * A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default. @@ -109,3 +116,7 @@ export class PosPopupController extends PosComponent { return this.topPopup === popup || popup.props.keepBehind; } } + +registry.category("main_components").add("PosPopupController", { + Component: PosPopupController, +}); diff --git a/addons/point_of_sale/static/src/js/PosComponent.js b/addons/point_of_sale/static/src/js/PosComponent.js index 1eba2538564..8d0af987c86 100644 --- a/addons/point_of_sale/static/src/js/PosComponent.js +++ b/addons/point_of_sale/static/src/js/PosComponent.js @@ -1,11 +1,16 @@ /** @odoo-module */ +import { useService } from "@web/core/utils/hooks"; import { LegacyComponent } from "@web/legacy/legacy_component"; let nextId = 0; export class PosComponent extends LegacyComponent { static components = {}; + setup() { + this.notification = useService("pos_notification"); + this.sound = useService("sound"); + } /** * This function is available to all Components that inherit this class. * The goal of this function is to show an awaitable dialog (popup) that @@ -42,7 +47,7 @@ export class PosComponent extends LegacyComponent { * @param {String} name 'bell' | 'error' */ playSound(name) { - this.trigger("play-sound", name); + this.sound.play(name); } /** * Control the SyncNotification component. @@ -52,10 +57,7 @@ export class PosComponent extends LegacyComponent { setSyncStatus(status, pending) { this.trigger("set-sync-status", { status, pending }); } - showNotification(message, duration = 2000) { - this.trigger("show-notification", { message, duration }); - } - closeNotification() { - this.trigger("close-notification"); + showNotification(message, duration) { + this.notification.add(message, duration); } } diff --git a/addons/point_of_sale/static/src/scss/pos.scss b/addons/point_of_sale/static/src/scss/pos.scss index b7a5f0812fb..e79d0530c2e 100644 --- a/addons/point_of_sale/static/src/scss/pos.scss +++ b/addons/point_of_sale/static/src/scss/pos.scss @@ -3676,24 +3676,6 @@ td { } } -/** - * Notification - */ - - .pos .notification { - width: 350px; - background-color: #333333AA; - color: #fff; - text-align: center; - border-radius: 2px; - padding: 16px; - position: fixed; - z-index: 1; - left: calc((100% - 350px - 32px)/2); - bottom: 5%; - font-size: 17px; -} - .pos .pos-topheader .status-buttons > div:not(.not-clickable):hover { background: rgba(0, 0, 0, .08); } diff --git a/addons/point_of_sale/static/src/utils.js b/addons/point_of_sale/static/src/utils.js new file mode 100644 index 00000000000..dffb1556cfe --- /dev/null +++ b/addons/point_of_sale/static/src/utils.js @@ -0,0 +1,43 @@ +/** @odoo-module */ + +import { reactive } from "@odoo/owl"; + +/** + * Creates a side-effect that runs based on the content of reactive objects. + * + * @template {object[]} T + * @param {(...args: [...T]) => void} cb callback for the effect + * @param {[...T]} deps the reactive objects that the effect depends on + */ +export function effect(cb, deps) { + const reactiveDeps = reactive(deps, () => { + cb(...reactiveDeps); + }); + cb(...reactiveDeps); +} + +/** + * Adds computed properties to a reactive object derived from multiples sources. + * + * @template {object} T + * @template {object[]} U + * @template {{[key: string]: (this: T, ...rest: [...U]) => unknown}} V + * @param {T} obj the reactive object on which to add the computed + * properties + * @param {[...U]} sources the reactive objects which are needed to compute + * the properties + * @param {V} descriptor the object containing methods to compute the + * properties + * @returns {T & {[key in keyof V]: ReturnType}} + */ +export function withComputedProperties(obj, sources, descriptor) { + for (const [key, compute] of Object.entries(descriptor)) { + effect( + (obj, sources) => { + obj[key] = compute.call(obj, ...sources); + }, + [obj, sources] + ); + } + return obj; +} diff --git a/addons/point_of_sale/static/src/xml/Chrome.xml b/addons/point_of_sale/static/src/xml/Chrome.xml index a12fa990fc0..ad4f76e0bfa 100644 --- a/addons/point_of_sale/static/src/xml/Chrome.xml +++ b/addons/point_of_sale/static/src/xml/Chrome.xml @@ -2,19 +2,19 @@ -
+
- +
- - + +
@@ -22,12 +22,12 @@
- +
- +

Connecting to devices

- - - - - - - -
diff --git a/addons/point_of_sale/static/src/xml/Notification.xml b/addons/point_of_sale/static/src/xml/Notification.xml deleted file mode 100644 index cf5d2de92d8..00000000000 --- a/addons/point_of_sale/static/src/xml/Notification.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
- - - -
-
- -
diff --git a/addons/point_of_sale/static/tests/unit/test_PosPopupController.js b/addons/point_of_sale/static/tests/unit/test_PosPopupController.js index 18ac4601d23..5cc8d2c96fe 100644 --- a/addons/point_of_sale/static/tests/unit/test_PosPopupController.js +++ b/addons/point_of_sale/static/tests/unit/test_PosPopupController.js @@ -6,6 +6,8 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import makeTestEnvironment from "web.test_env"; import testUtils from "web.test_utils"; import { mount } from "@web/../tests/helpers/utils"; +import { makeTestEnv } from "@web/../tests/helpers/mock_env"; +import { registry } from "@web/core/registry"; const { EventBus, useSubEnv, xml } = owl; @@ -35,29 +37,46 @@ CustomPopup2.template = xml/* html */ `
`; -QUnit.module("unit tests for PosPopupController"); +let env; +QUnit.module("unit tests for PosPopupController", { + async beforeEach() { + const posbus = new EventBus(); + const makeService = (obj) => ({ + start() { + return obj; + }, + }); + const legacyEnv = makeTestEnvironment(); + registry.category("services").add("pos_notification", makeService({ add() {} })); + registry.category("services").add("sound", makeService({ play() {} })); + registry.category("services").add("pos", makeService({ legacyEnv })); + env = await makeTestEnv(); + for (const service of ["pos", "pos_notification", "sound"]) { + legacyEnv.services[service] = env.services[service]; + } + env.posbus = posbus; + legacyEnv.posbus = posbus; + }, +}); QUnit.test("allow multiple popups at the same time", async function (assert) { assert.expect(12); - class Root extends PosComponent { static components = { PosPopupController }; setup() { super.setup(); useSubEnv({ isDebug: () => false, - posbus: new EventBus(), }); } } - Root.env = makeTestEnvironment(); Root.template = xml/* html */ `
`; - const root = await mount(Root, testUtils.prepareTarget()); + const root = await mount(Root, testUtils.prepareTarget(), { env }); // Check 1 popup let popup1Promise = root.showPopup(CustomPopup1, {}); @@ -117,18 +136,16 @@ QUnit.test("pressing cancel/confirm key should only close the top popup", async super.setup(); useSubEnv({ isDebug: () => false, - posbus: new EventBus(), }); } } - Root.env = makeTestEnvironment(); Root.template = xml/* html */ `
`; - const root = await mount(Root, testUtils.prepareTarget()); + const root = await mount(Root, testUtils.prepareTarget(), { env }); const popup1Promise = root.showPopup(CustomPopup1, { confirmKey: "Enter", diff --git a/addons/pos_hr_restaurant/static/src/js/Chrome.js b/addons/pos_hr_restaurant/static/src/js/Chrome.js index 2382a2fab15..673c17d2aa4 100644 --- a/addons/pos_hr_restaurant/static/src/js/Chrome.js +++ b/addons/pos_hr_restaurant/static/src/js/Chrome.js @@ -1,11 +1,11 @@ /* @odoo-module alias=pos_restaurant_hr.chrome */ -import { Chrome } from '@point_of_sale/js/Chrome'; +import { Chrome } from "@point_of_sale/js/Chrome"; import { patch } from "@web/core/utils/patch"; patch(Chrome.prototype, "pos_hr_restaurant.Chrome", { //@override _shouldResetIdleTimer() { - return this._super() && this.state.tempScreen?.name !== 'LoginScreen'; - } + return this._super() && this.pos.tempScreen?.name !== "LoginScreen"; + }, }); diff --git a/addons/pos_restaurant/static/src/js/Chrome.js b/addons/pos_restaurant/static/src/js/Chrome.js index 7d531055dec..02d4a324cc9 100644 --- a/addons/pos_restaurant/static/src/js/Chrome.js +++ b/addons/pos_restaurant/static/src/js/Chrome.js @@ -58,7 +58,7 @@ patch(Chrome.prototype, "pos_restaurant.Chrome", { this.env.posbus.trigger("close-popups-but-error", { resolve }) ); if (isPopupClosed) { - if (this.state.tempScreen) { + if (this.pos.tempScreen) { this.trigger("close-temp-screen"); } const table = this.env.pos.table; @@ -73,12 +73,12 @@ patch(Chrome.prototype, "pos_restaurant.Chrome", { }, _shouldResetIdleTimer() { const stayPaymentScreen = - this.mainScreen.name === "PaymentScreen" && + this.pos.mainScreen.name === "PaymentScreen" && this.env.pos.get_order().paymentlines.length > 0; return ( this.env.pos.config.iface_floorplan && !stayPaymentScreen && - this.mainScreen.name !== "FloorScreen" + this.pos.mainScreen.name !== "FloorScreen" ); }, __showScreen() {