From 0d537e58a934d97ef1d724107e962dca29b3cb45 Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Wed, 1 Feb 2023 14:02:41 +0000 Subject: [PATCH] [REF] pos*: move show(Temp)Screen, make number_buffer and popup services *: l10n_fr_pos_cert, point_of_sale, pos_discount, pos_hr, pos_hr_restaurant, pos_loyalty, pos_mercury, pos_restaurant, pos_sale, pos_sale_product_configurator, pos_six, pos_stripe Continuing to move things out of the Chrome god component, this commit does the following things: - moves the showScreen and showTempScreen methods and the corresponding closing methods into the pos store so that components that need them can access them through the pos service in a more explicit way. To that end, these methods have been removed from PosComponent and Gui. - introduces a popup service instead of using a bus in the env, this is mechanically very similar but closer to what's done in web and hopefully a first step to using the dialog service instead. It also removes the showPopup method from the PosComponent and Gui, to make components depending on the popup service explicit. - makes the number buffer into a service instead of being a directly imported singleton, which will enable it to be used with hooks instead at a later point, and allows easier dependency injection in tests. - removes showNotification from the PosComponent and Gui, the notification system was already converted to a service in a previous commit for the same reasons as showPopup and the end goal is to use the notification service from web to reduce duplication. closes odoo/odoo#111844 Related: odoo/enterprise#36668 Signed-off-by: Samuel Degueldre --- .../l10n_fr_pos_cert/static/src/js/Chrome.js | 2 +- addons/l10n_fr_pos_cert/static/src/js/pos.js | 5 +- .../static/src/app/debug/debug_widget.js | 26 +-- .../src/app/error_handlers/error_handlers.js | 16 +- .../static/src/app/navbar/navbar.js | 2 +- .../number_buffer_service.js} | 16 +- .../static/src/app/popup/popup_controller.xml | 14 ++ .../static/src/app/popup/popup_service.js | 62 ++++++ .../point_of_sale/static/src/app/pos_hook.js | 11 ++ .../point_of_sale/static/src/app/pos_store.js | 56 ++++-- addons/point_of_sale/static/src/js/Chrome.js | 88 +++------ .../src/js/ChromeWidgets/CashMoveButton.js | 19 +- .../src/js/ChromeWidgets/HeaderButton.js | 10 +- .../src/js/ChromeWidgets/SaleDetailsButton.js | 8 +- .../src/js/ChromeWidgets/TicketButton.js | 7 +- addons/point_of_sale/static/src/js/Gui.js | 38 +--- .../src/js/Misc/AbstractReceiptScreen.js | 6 +- .../src/js/Misc/IndependentToOrderScreen.js | 8 +- .../src/js/Popups/AbstractAwaitablePopup.js | 29 ++- .../static/src/js/Popups/CashOpeningPopup.js | 4 +- .../static/src/js/Popups/ClosePosPopup.js | 17 +- .../static/src/js/Popups/EditListPopup.js | 2 +- .../static/src/js/Popups/ErrorPopup.js | 4 +- .../static/src/js/Popups/NumberPopup.js | 10 +- .../static/src/js/Popups/OfflineErrorPopup.js | 6 + .../src/js/Popups/PosPopupController.js | 122 ------------ .../static/src/js/PosComponent.js | 48 ----- .../PartnerListScreen/PartnerDetailsEdit.js | 8 +- .../PartnerListScreen/PartnerListScreen.js | 11 +- .../js/Screens/PaymentScreen/PaymentScreen.js | 97 +++++----- .../OrderlineCustomerNoteButton.js | 6 +- .../ControlButtons/ProductInfoButton.js | 5 +- .../ControlButtons/RefundButton.js | 4 +- .../ControlButtons/SetFiscalPositionButton.js | 5 +- .../ControlButtons/SetPricelistButton.js | 5 +- .../js/Screens/ProductScreen/OrderWidget.js | 11 +- .../js/Screens/ProductScreen/ProductScreen.js | 54 +++--- .../Screens/ProductScreen/ProductsWidget.js | 7 +- .../ProductsWidgetControlPanel.js | 6 +- .../js/Screens/ReceiptScreen/ReceiptScreen.js | 8 +- .../src/js/Screens/ScaleScreen/ScaleScreen.js | 6 +- .../ControlButtons/InvoiceButton.js | 11 +- .../ControlButtons/ReprintReceiptButton.js | 4 +- .../TicketScreen/ReprintReceiptScreen.js | 7 +- .../js/Screens/TicketScreen/TicketScreen.js | 29 +-- .../static/src/js/custom_hooks.js | 15 +- addons/point_of_sale/static/src/js/models.js | 108 ++++++----- addons/point_of_sale/static/src/js/payment.js | 1 + .../point_of_sale/static/src/js/printers.js | 5 +- addons/point_of_sale/static/src/utils.js | 6 + .../point_of_sale/static/src/xml/Chrome.xml | 10 +- .../src/xml/Popups/PosPopupController.xml | 14 -- .../Screens/PaymentScreen/PaymentScreen.xml | 4 +- ...ffer.js => number_buffer_service_tests.js} | 25 ++- .../tests/unit/popup_controller_tests.js | 152 +++++++++++++++ .../tests/unit/test_PosPopupController.js | 181 ------------------ .../pos_adyen/static/src/js/payment_adyen.js | 3 +- .../static/src/js/DiscountButton.js | 7 +- addons/pos_hr/static/src/js/CashierName.js | 4 +- addons/pos_hr/static/src/js/Chrome.js | 2 +- .../pos_hr/static/src/js/HeaderLockButton.js | 4 +- addons/pos_hr/static/src/js/LoginScreen.js | 10 +- .../static/src/js/SelectCashierMixin.js | 11 +- addons/pos_hr_restaurant/__manifest__.py | 2 +- .../static/src/app/pos_store.js | 11 ++ .../pos_hr_restaurant/static/src/js/Chrome.js | 11 -- .../src/js/ControlButtons/PromoCodeButton.js | 5 +- .../src/js/ControlButtons/RewardButton.js | 11 +- .../src/js/ControlButtons/eWalletButton.js | 12 +- addons/pos_loyalty/static/src/js/Loyalty.js | 5 +- .../static/src/js/PaymentScreen.js | 2 +- .../static/src/js/ProductScreen.js | 18 +- .../pos_loyalty/static/src/js/TicketScreen.js | 12 +- .../static/src/js/PaymentScreen.js | 39 ++-- .../static/src/js/PosPopupController.js | 9 - .../static/src/js/ProductScreen.js | 2 +- .../navbar}/BackToFloorButton.js | 4 +- .../static/src/app/{ => navbar}/navbar.js | 2 +- .../static/src/app/{ => navbar}/navbar.xml | 0 .../static/src/app/popup/popup_service.js | 24 +++ .../static/src/app/pos_store.js | 89 +++++++++ addons/pos_restaurant/static/src/js/Chrome.js | 85 +------- .../src/js/ChromeWidgets/TicketButton.js | 2 +- .../src/js/Popups/PosPopupController.js | 25 --- .../static/src/js/Screens/BillScreen.js | 4 +- .../src/js/Screens/FloorScreen/FloorScreen.js | 14 +- .../ControlButtons/OrderlineNoteButton.js | 5 +- .../ControlButtons/PrintBillButton.js | 4 +- .../ControlButtons/SplitBillButton.js | 4 +- .../ControlButtons/SubmitOrderButton.js | 4 +- .../ControlButtons/TableGuestsButton.js | 7 +- .../ControlButtons/TransferOrderButton.js | 4 +- .../js/Screens/ReceiptScreen/ReceiptScreen.js | 5 +- .../SplitBillScreen/SplitBillScreen.js | 6 +- .../static/src/js/Screens/TicketScreen.js | 8 +- .../static/src/js/Screens/TipScreen.js | 14 +- .../static/src/xml/Screens/BillScreen.xml | 2 +- .../static/src/xml/Screens/TipScreen.xml | 2 +- .../OrderManagementScreen/SaleOrderFetcher.js | 2 +- .../SaleOrderManagementScreen.js | 24 +-- .../static/src/js/SetSaleOrderButton.js | 8 +- .../static/src/js/models.js | 3 +- addons/pos_six/static/src/js/payment_six.js | 5 +- .../static/src/js/payment_stripe.js | 9 +- 104 files changed, 983 insertions(+), 1003 deletions(-) rename addons/point_of_sale/static/src/{js/Misc/NumberBuffer.js => app/number_buffer_service.js} (97%) create mode 100644 addons/point_of_sale/static/src/app/popup/popup_controller.xml create mode 100644 addons/point_of_sale/static/src/app/popup/popup_service.js create mode 100644 addons/point_of_sale/static/src/app/pos_hook.js delete mode 100644 addons/point_of_sale/static/src/js/Popups/PosPopupController.js delete mode 100644 addons/point_of_sale/static/src/xml/Popups/PosPopupController.xml rename addons/point_of_sale/static/tests/unit/{test_NumberBuffer.js => number_buffer_service_tests.js} (72%) create mode 100644 addons/point_of_sale/static/tests/unit/popup_controller_tests.js delete mode 100644 addons/point_of_sale/static/tests/unit/test_PosPopupController.js create mode 100644 addons/pos_hr_restaurant/static/src/app/pos_store.js delete mode 100644 addons/pos_hr_restaurant/static/src/js/Chrome.js delete mode 100644 addons/pos_mercury/static/src/js/PosPopupController.js rename addons/pos_restaurant/static/src/{js/ChromeWidgets => app/navbar}/BackToFloorButton.js (80%) rename addons/pos_restaurant/static/src/app/{ => navbar}/navbar.js (75%) rename addons/pos_restaurant/static/src/app/{ => navbar}/navbar.xml (100%) create mode 100644 addons/pos_restaurant/static/src/app/popup/popup_service.js create mode 100644 addons/pos_restaurant/static/src/app/pos_store.js delete mode 100644 addons/pos_restaurant/static/src/js/Popups/PosPopupController.js diff --git a/addons/l10n_fr_pos_cert/static/src/js/Chrome.js b/addons/l10n_fr_pos_cert/static/src/js/Chrome.js index 60744159b00..e281108858b 100644 --- a/addons/l10n_fr_pos_cert/static/src/js/Chrome.js +++ b/addons/l10n_fr_pos_cert/static/src/js/Chrome.js @@ -13,7 +13,7 @@ patch(Chrome.prototype, "l10n_fr_pos_cert.Chrome", { limitDate.setDate(limitDate.getDate() + 1); if (limitDate.getTime() < now) { const info = await this.env.pos.getClosePosInfo(); - this.showPopup(ClosePosPopup, { info: info }); + this.popup.add(ClosePosPopup, { info }); } } }, diff --git a/addons/l10n_fr_pos_cert/static/src/js/pos.js b/addons/l10n_fr_pos_cert/static/src/js/pos.js index 3633c585e71..a54cc6377a5 100644 --- a/addons/l10n_fr_pos_cert/static/src/js/pos.js +++ b/addons/l10n_fr_pos_cert/static/src/js/pos.js @@ -1,6 +1,5 @@ /** @odoo-module */ -import { Gui } from "@point_of_sale/js/Gui"; import { PosGlobalState, Order, Orderline } from "@point_of_sale/js/models"; import core from "web.core"; import { patch } from "@web/core/utils/patch"; @@ -12,7 +11,7 @@ patch(PosGlobalState.prototype, "l10n_fr_pos_cert.PosGlobalState", { is_french_country() { var french_countries = ["FR", "MF", "MQ", "NC", "PF", "RE", "GF", "GP", "TF"]; if (!this.company.country) { - Gui.showPopup(ErrorPopup, { + this.env.services.popup.add(ErrorPopup, { title: _t("Missing Country"), body: _.str.sprintf( _t("The company %s doesn't have a country set."), @@ -60,7 +59,7 @@ patch(Order.prototype, "l10n_fr_pos_cert.Order", { this.pos.is_french_country() && this.get_orderlines().length ) { - Gui.showPopup(ErrorPopup, { + this.env.services.popup.add(ErrorPopup, { title: _t("Fiscal Data Module error"), body: _t("Deleting of orders is not allowed."), }); diff --git a/addons/point_of_sale/static/src/app/debug/debug_widget.js b/addons/point_of_sale/static/src/app/debug/debug_widget.js index 7b39a553da1..3bf750f031d 100644 --- a/addons/point_of_sale/static/src/app/debug/debug_widget.js +++ b/addons/point_of_sale/static/src/app/debug/debug_widget.js @@ -1,15 +1,14 @@ /** @odoo-module */ import { parse } from "web.field_utils"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; import { PosComponent } from "@point_of_sale/js/PosComponent"; 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"; +import { useBus, useService } from "@web/core/utils/hooks"; -import { onMounted, onWillUnmount, useRef, useState } from "@odoo/owl"; +import { useRef, useState } from "@odoo/owl"; export class DebugWidget extends PosComponent { static components = { Transition, Draggable }; @@ -17,6 +16,9 @@ export class DebugWidget extends PosComponent { setup() { super.setup(); this.debug = useService("debug"); + this.popup = useService("popup"); + const numberBuffer = useService("number_buffer"); + useBus(numberBuffer, "buffer-update", this._onBufferUpdate); this.state = useState({ barcodeInput: "", weightInput: "", @@ -49,14 +51,6 @@ export class DebugWidget extends PosComponent { }).bind(this) ); } - - onMounted(() => { - numberBuffer.on("buffer-update", this, this._onBufferUpdate); - }); - - onWillUnmount(() => { - numberBuffer.off("buffer-update", this, this._onBufferUpdate); - }); } toggleWidget() { this.state.isShown = !this.state.isShown; @@ -82,7 +76,7 @@ export class DebugWidget extends PosComponent { await this.env.barcode_reader.scan(ean); } async deleteOrders() { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Delete Paid Orders ?"), body: this.env._t( "This operation will permanently destroy all paid orders from the local storage. You will lose all the data. This operation cannot be undone." @@ -94,7 +88,7 @@ export class DebugWidget extends PosComponent { } } async deleteUnpaidOrders() { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Delete Unpaid Orders ?"), body: this.env._t( "This operation will destroy all unpaid orders in the browser. You will lose all the unsaved data and exit the point of sale. This operation cannot be undone." @@ -148,14 +142,14 @@ export class DebugWidget extends PosComponent { const file = event.target.files[0]; if (file) { const report = this.env.pos.import_orders(await file.text()); - await this.showPopup(OrderImportPopup, { report }); + await this.popup.add(OrderImportPopup, { report }); } } refreshDisplay() { this.env.proxy.message("display_refresh", {}); } - _onBufferUpdate(buffer) { - this.state.buffer = buffer; + _onBufferUpdate({ detail: value }) { + this.state.buffer = value; } get bufferRepr() { return `"${this.state.buffer}"`; diff --git a/addons/point_of_sale/static/src/app/error_handlers/error_handlers.js b/addons/point_of_sale/static/src/app/error_handlers/error_handlers.js index 8e9ab9ef39d..ac7901c9095 100644 --- a/addons/point_of_sale/static/src/app/error_handlers/error_handlers.js +++ b/addons/point_of_sale/static/src/app/error_handlers/error_handlers.js @@ -3,7 +3,7 @@ import { registry } from "@web/core/registry"; import { odooExceptionTitleMap } from "@web/core/errors/error_dialogs"; import { ConnectionLostError, RPCError } from "@web/core/network/rpc_service"; -import { Gui, GuiNotReadyError } from "@point_of_sale/js/Gui"; +import { GuiNotReadyError } from "@point_of_sale/js/Gui"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { ErrorTracebackPopup } from "@point_of_sale/js/Popups/ErrorTracebackPopup"; import { OfflineErrorPopup } from "@point_of_sale/js/Popups/OfflineErrorPopup"; @@ -18,9 +18,9 @@ function rpcErrorHandler(env, error, originalError) { const { message, data } = error; if (odooExceptionTitleMap.has(error.exceptionName)) { const title = odooExceptionTitleMap.get(error.exceptionName).toString(); - Gui.showPopup(ErrorPopup, { title, body: data.message }); + env.services.popup.add(ErrorPopup, { title, body: data.message }); } else { - Gui.showPopup(ErrorTracebackPopup, { + env.services.popup.add(ErrorTracebackPopup, { title: message, body: data.message + "\n" + data.debug + "\n", }); @@ -33,7 +33,7 @@ registry.category("error_handlers").add("rpcErrorHandler", rpcErrorHandler); function offlineErrorHandler(env, error, originalError) { error = identifyError(originalError); if (error instanceof ConnectionLostError) { - Gui.showPopup(OfflineErrorPopup, { + env.services.popup.add(OfflineErrorPopup, { title: env._t("Couldn't connect to the server"), body: env._t( "The operation couldn't be completed because you are offline. Check your internet connection and try again." @@ -45,14 +45,14 @@ function offlineErrorHandler(env, error, originalError) { registry.category("error_handlers").add("offlineErrorHandler", offlineErrorHandler); function defaultErrorHandler(env, error, originalError) { - error = identifyError(originalError); + originalError = identifyError(originalError); if (error instanceof Error) { - Gui.showPopup(ErrorTracebackPopup, { - title: `${error.name}: ${error.message}`, + env.services.popup.add(ErrorTracebackPopup, { + title: `${originalError.name}: ${originalError.message}`, body: error.traceback, }); } else { - Gui.showPopup(ErrorPopup, { + env.services.popup.add(ErrorPopup, { title: env._t("Unknown Error"), body: env._t("Unable to show information about this error."), }); 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 3ab31a7ad2b..2824a1cc9a4 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.js +++ b/addons/point_of_sale/static/src/app/navbar/navbar.js @@ -1,7 +1,7 @@ /** @odoo-module */ import { PosComponent } from "@point_of_sale/js/PosComponent"; -import { usePos } from "@point_of_sale/app/pos_store"; +import { usePos } from "@point_of_sale/app/pos_hook"; import { useService } from "@web/core/utils/hooks"; import { CashierName } from "@point_of_sale/js/ChromeWidgets/CashierName"; diff --git a/addons/point_of_sale/static/src/js/Misc/NumberBuffer.js b/addons/point_of_sale/static/src/app/number_buffer_service.js similarity index 97% rename from addons/point_of_sale/static/src/js/Misc/NumberBuffer.js rename to addons/point_of_sale/static/src/app/number_buffer_service.js index f91eb851e5d..d626f17c714 100644 --- a/addons/point_of_sale/static/src/js/Misc/NumberBuffer.js +++ b/addons/point_of_sale/static/src/app/number_buffer_service.js @@ -4,7 +4,7 @@ import { useListener } from "@web/core/utils/hooks"; import { parse } from "web.field_utils"; import { barcodeService } from "@barcodes/barcode_service"; import { _t } from "web.core"; -import { Gui } from "@point_of_sale/js/Gui"; +import { registry } from "@web/core/registry"; const { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } = owl; const INPUT_KEYS = new Set( @@ -60,10 +60,11 @@ const getDefaultConfig = () => ({ * - Write more integration tests. NumberPopup can be used as test component. */ class NumberBuffer extends EventBus { - constructor() { + constructor({ sound }) { super(); this.isReset = false; this.bufferHolderStack = []; + this.sound = sound; } /** * @returns {String} value of the buffer, e.g. '-95.79' @@ -294,7 +295,7 @@ class NumberBuffer extends EventBus { if (isFirstInput) { this.state.buffer = "" + input; } else if (this.state.buffer.length > 12) { - Gui.playSound("bell"); + this.sound.play("bell"); } else { this.state.buffer += input; } @@ -317,4 +318,11 @@ class NumberBuffer extends EventBus { } } -export const numberBuffer = new NumberBuffer(); +export const numberBuffer = { + dependencies: ["sound"], + start(env, { sound }) { + return new NumberBuffer({ sound }); + }, +}; + +registry.category("services").add("number_buffer", numberBuffer); diff --git a/addons/point_of_sale/static/src/app/popup/popup_controller.xml b/addons/point_of_sale/static/src/app/popup/popup_controller.xml new file mode 100644 index 00000000000..20de6af7a9c --- /dev/null +++ b/addons/point_of_sale/static/src/app/popup/popup_controller.xml @@ -0,0 +1,14 @@ + + + + +
+ + + +
+
+ +
diff --git a/addons/point_of_sale/static/src/app/popup/popup_service.js b/addons/point_of_sale/static/src/app/popup/popup_service.js new file mode 100644 index 00000000000..142b563a545 --- /dev/null +++ b/addons/point_of_sale/static/src/app/popup/popup_service.js @@ -0,0 +1,62 @@ +/** @odoo-module */ + +import { reactive } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { PosComponent } from "@point_of_sale/js/PosComponent"; +import { usePos } from "@point_of_sale/app/pos_hook"; +export class PopupContainer extends PosComponent { + static template = "point_of_sale.PopupContainer"; + setup() { + // FIXME POSREF: remove this after PosComponent is removed. + this.__owl__.childEnv = usePos().legacyEnv; + } +} + +// FIXME POSREF: probably should use the main dialog service from web long term. +export const popupService = { + start() { + const popups = reactive({}); + registry.category("main_components").add("PopupContainer", { + Component: PopupContainer, + props: { popups }, + }); + let popupId = 0; + return { + /** + * Displays a popup over the interface. + * + * @param {AbstractAwaitablePopup} component the popup component to + * open + * @param {Object} props props for the popup component + * @returns {Promise} a Promise that fulfills when the popup + * is closed (confirmed or canceled) + */ + add(component, props) { + return new Promise((resolve) => { + const id = ++popupId; + popups[id] = { + component, + props: { + keepBehind: false, + cancelKey: "Escape", + confirmKey: "Enter", + // FIXME POSREF assigning the default props by hand defeats the point of default props + ...component.defaultProps, + ...props, + id, + resolve, + close(response) { + delete popups[id]; + resolve(response); + }, + }, + }; + }); + }, + // exposed so that overrides can use it, should not be read from outside. Maybe we should just implement closePopupsButError directly in pos instead of pos_restaurant + popups, + }; + }, +}; + +registry.category("services").add("popup", popupService); diff --git a/addons/point_of_sale/static/src/app/pos_hook.js b/addons/point_of_sale/static/src/app/pos_hook.js new file mode 100644 index 00000000000..73c1ad3778e --- /dev/null +++ b/addons/point_of_sale/static/src/app/pos_hook.js @@ -0,0 +1,11 @@ +/** @odoo-module */ + +import { useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; + +/** + * @returns {import("./pos_store").PosStore} + */ +export function usePos() { + return useState(useService("pos")); +} 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 13c3f006d8d..a86aab625d7 100644 --- a/addons/point_of_sale/static/src/app/pos_store.js +++ b/addons/point_of_sale/static/src/app/pos_store.js @@ -4,10 +4,10 @@ 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 { reactive, useState, markRaw } from "@odoo/owl"; +import { reactive, markRaw } from "@odoo/owl"; +import { Reactive } from "@point_of_sale/utils"; -export class PosStore { +export class PosStore extends Reactive { /** @type {'LOADING' | 'READY' | 'CLOSING'} */ uiState = "LOADING"; hasBigScrollBars = false; @@ -15,26 +15,52 @@ export class PosStore { mainScreen = { name: null, component: null }; tempScreen = null; legacyEnv = legacyEnv; - constructor() { + globalState = new PosGlobalState({ env: markRaw(legacyEnv) }); + + static serviceDependencies = ["popup"]; + constructor({ popup }) { + super(); + this.popup = popup; this.setup(); } // use setup instead of constructor because setup can be patched. - setup() { - this.globalState = new PosGlobalState({ env: markRaw(legacyEnv) }); + setup() {} + + showScreen(name, props) { + const component = registry.category("pos_screens").get(name); + this.mainScreen = { component, props }; + // Save the screen to the order so that it is shown again when the order is selected. + if (component.storeOnOrder ?? true) { + this.globalState.get_order()?.set_screen_data({ name, props }); + } + } + + closeScreen() { + const { name: screenName } = this.globalState.get_order().get_screen_data(); + this.showScreen(screenName); + } + + // FIXME POSREF get rid of temp screens entirely? + showTempScreen(name, props = {}) { + return new Promise((resolve) => { + this.tempScreen = { + name, + component: registry.category("pos_screens").get(name), + props: { ...props, resolve }, + }; + }); + } + + closeTempScreen() { + this.tempScreen = null; } } export const posService = { - start() { - return reactive(new PosStore()); + dependencies: PosStore.serviceDependencies, + start(env, deps) { + return reactive(new PosStore(deps)); }, }; 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 846a8714b87..ab7776b45f1 100644 --- a/addons/point_of_sale/static/src/js/Chrome.js +++ b/addons/point_of_sale/static/src/js/Chrome.js @@ -4,13 +4,11 @@ import { loadCSS } from "@web/core/assets"; import { useListener, useBus, useService } from "@web/core/utils/hooks"; import BarcodeParser from "barcodes.BarcodeParser"; import { PosComponent } from "@point_of_sale/js/PosComponent"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; -import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen"; import { batched } from "@point_of_sale/js/utils"; import { debounce } from "@web/core/utils/timing"; import { Transition } from "@web/core/transition"; import { MainComponentsContainer } from "@web/core/main_components_container"; -import { WithEnv } from "@web/core/utils/components"; +import { WithEnv, ErrorHandler } from "@web/core/utils/components"; import { Navbar } from "@point_of_sale/app/navbar/navbar"; // ChromeAdapter imports @@ -31,7 +29,7 @@ import { reactive, onWillUnmount, } from "@odoo/owl"; -import { usePos } from "@point_of_sale/app/pos_store"; +import { usePos } from "@point_of_sale/app/pos_hook"; /** * Chrome is the root component of the PoS App. @@ -40,6 +38,7 @@ export class Chrome extends PosComponent { static template = "Chrome"; // FIXME POSREF namespace templates setup() { this.pos = usePos(); + this.popup = useService("popup"); // BEGIN ChromeAdapter ProductScreen.sortControlButtons(); const legacyActionManager = useService("legacy_action_manager"); @@ -59,7 +58,14 @@ export class Chrome extends PosComponent { window.posmodel = this.pos.globalState.debug ? reactivePos : this.pos.globalState; this.wowlEnv = this.env; - for (const service of ["pos", "sound", "debug", "pos_notification"]) { + for (const service of [ + "pos", + "sound", + "debug", + "pos_notification", + "number_buffer", + "popup", + ]) { env.services[service] = this.wowlEnv.services[service]; } @@ -83,15 +89,12 @@ export class Chrome extends PosComponent { super.setup(); useExternalListener(window, "beforeunload", this._onBeforeUnload); - useListener("show-main-screen", this.__showScreen); - useListener("show-temp-screen", this.__showTempScreen); - useListener("close-temp-screen", this.__closeTempScreen); useListener("close-pos", this._closePos); useListener("loading-skip-callback", () => this.env.proxy.stop_searching()); useListener("set-sync-status", this._onSetSyncStatus); useListener("connect-to-proxy", this.connect_to_proxy); useBus(this.env.posbus, "start-cash-control", this.openCashControl); - numberBuffer.activate(); + useService("number_buffer").activate(); useSubEnv({ pos: reactive( @@ -170,7 +173,8 @@ export class Chrome extends PosComponent { ? this.env.pos.config.iface_start_categ_id[0] : 0; this.pos.uiState = "READY"; - this._showStartScreen(); + const { name, props } = this.startScreen; + this.pos.showScreen(name, props); setTimeout(() => this._runBackgroundTasks()); } catch (error) { let title = "Unknown Error"; @@ -198,7 +202,7 @@ export class Chrome extends PosComponent { } } - return this.showPopup(ErrorTracebackPopup, { title, body, exitButtonIsShown: true }); + return this.popup.add(ErrorTracebackPopup, { title, body, exitButtonIsShown: true }); } registry.category("main_components").add("BlockUI", BlockUiFromRegistry); @@ -290,7 +294,7 @@ export class Chrome extends PosComponent { openCashControl() { if (this.shouldShowCashControl()) { - this.showPopup(CashOpeningPopup, { keepBehind: true }); + this.popup.add(CashOpeningPopup, { keepBehind: true }); } } @@ -302,52 +306,6 @@ export class Chrome extends PosComponent { // EVENT HANDLERS // - _showStartScreen() { - const { name, props } = this.startScreen; - this.showScreen(name, props); - } - _getSavedScreen(order) { - return order.get_screen_data(); - } - __showTempScreen(event) { - const { name, props, resolve } = event.detail; - this.pos.tempScreen = { - name, - component: registry.category("pos_screens").get(name), - props: { ...props, resolve }, - }; - } - __closeTempScreen() { - 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.pos.mainScreen = { name, component, props }; - - // 2. Save the screen to the order. - // - This screen is shown when the order is selected. - if ( - !(component.prototype instanceof IndependentToOrderScreen) && - name !== "ReprintReceiptScreen" - ) { - this._setScreenData(name, props); - } - } - /** - * Set the latest screen to the current order. This is done so that - * when the order is selected again, the ui returns to the latest screen - * saved in the order. - * - * @param {string} name Screen name - * @param {Object} props props for the Screen component - */ - _setScreenData(name, props) { - const order = this.env.pos.get_order(); - if (order) { - order.set_screen_data({ name, props }); - } - } async _closePos() { // If pos is not properly loaded, we just go back to /web without // doing anything in the order data. @@ -379,7 +337,7 @@ export class Chrome extends PosComponent { "not close the session before the issue " + "has been resolved." ); - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Offline Orders"), body: reason, }); @@ -473,6 +431,17 @@ export class Chrome extends PosComponent { get showCashMoveButton() { return this.env.pos && this.env.pos.config && this.env.pos.config.cash_control; } + /** + * Unmounts the tempScreen on error and dispatches the error in a separate + * stack so that it can be handled by the error service and display an error + * popup. + * + * @param {any} err the error that was thrown in the temp screen. + */ + onTempScreenError(err) { + this.pos.tempScreen = null; + Promise.reject(err); + } } Object.defineProperty(Chrome, "components", { get() { @@ -481,6 +450,7 @@ Object.defineProperty(Chrome, "components", { Transition, MainComponentsContainer, WithEnv, + ErrorHandler, Navbar, }, PosComponent.components diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/CashMoveButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/CashMoveButton.js index 09d643886ac..28e635c4963 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/CashMoveButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/CashMoveButton.js @@ -3,8 +3,9 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { _lt } from "web.core"; import { renderToString } from "@web/core/utils/render"; -import { CashMovePopup } from "../Popups/CashMovePopup"; -import { ErrorPopup } from "../Popups/ErrorPopup"; +import { CashMovePopup } from "@point_of_sale/js/Popups/CashMovePopup"; +import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { useService } from "@web/core/utils/hooks"; const TRANSLATED_CASH_MOVE_TYPE = { in: _lt("in"), @@ -14,8 +15,14 @@ const TRANSLATED_CASH_MOVE_TYPE = { export class CashMoveButton extends PosComponent { static template = "point_of_sale.CashMoveButton"; + setup() { + super.setup(...arguments); + this.popup = useService("popup"); + this.notification = useService("pos_notification"); + } + async onClick() { - const { confirmed, payload } = await this.showPopup(CashMovePopup); + const { confirmed, payload } = await this.popup.add(CashMovePopup); if (!confirmed) { return; } @@ -23,7 +30,7 @@ export class CashMoveButton extends PosComponent { const translatedType = TRANSLATED_CASH_MOVE_TYPE[type]; const formattedAmount = this.env.pos.format_currency(amount); if (!amount) { - return this.showNotification( + return this.notification.add( _.str.sprintf(this.env._t("Cash in/out of %s is ignored."), formattedAmount), 3000 ); @@ -40,13 +47,13 @@ export class CashMoveButton extends PosComponent { }); const printResult = await this.env.proxy.printer.print_receipt(renderedReceipt); if (!printResult.successful) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: printResult.message.title, body: printResult.message.body, }); } } - this.showNotification( + this.notification.add( _.str.sprintf(this.env._t("Successfully made a cash %s of %s."), type, formattedAmount), 3000 ); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js index 24a32a7352a..41d5fc6efa7 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js @@ -1,15 +1,21 @@ /** @odoo-module */ import { PosComponent } from "@point_of_sale/js/PosComponent"; -import { ClosePosPopup } from "../Popups/ClosePosPopup"; +import { useService } from "@web/core/utils/hooks"; +import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup"; // Previously HeaderButtonWidget // This is the close session button export class HeaderButton extends PosComponent { static template = "HeaderButton"; + setup() { + super.setup(...arguments); + this.popup = useService("popup"); + } + async onClick() { const info = await this.env.pos.getClosePosInfo(); - this.showPopup(ClosePosPopup, { info: info, keepBehind: true }); + this.popup.add(ClosePosPopup, { info: info, keepBehind: true }); } } diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js index cc02270d018..b57b6942806 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js @@ -1,12 +1,18 @@ /** @odoo-module */ import { PosComponent } from "@point_of_sale/js/PosComponent"; +import { useService } from "@web/core/utils/hooks"; import { renderToString } from "@web/core/utils/render"; import { ErrorPopup } from "../Popups/ErrorPopup"; export class SaleDetailsButton extends PosComponent { static template = "SaleDetailsButton"; + setup() { + super.setup(...arguments); + this.popup = useService("popup"); + } + async onClick() { // IMPROVEMENT: Perhaps put this logic in a parent component // so that for unit testing, we can check if this simple @@ -25,7 +31,7 @@ export class SaleDetailsButton extends PosComponent { ); const printResult = await this.env.proxy.printer.print_receipt(report); if (!printResult.successful) { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: printResult.message.title, body: printResult.message.body, }); diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js index a11b33771f5..f382cd39c7f 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js @@ -1,7 +1,8 @@ /** @odoo-module */ -import { usePos } from "@point_of_sale/app/pos_store"; +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; +import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScreen"; export class TicketButton extends PosComponent { static template = "TicketButton"; @@ -13,11 +14,11 @@ export class TicketButton extends PosComponent { if (this.isTicketScreenShown) { this.env.posbus.trigger("ticket-button-clicked"); } else { - this.showScreen("TicketScreen"); + this.pos.showScreen("TicketScreen"); } } get isTicketScreenShown() { - return this.pos.mainScreen.name === "TicketScreen"; + return this.pos.mainScreen.component === TicketScreen; } get count() { if (this.env.pos) { diff --git a/addons/point_of_sale/static/src/js/Gui.js b/addons/point_of_sale/static/src/js/Gui.js index ad7040df0e2..ef3dc09f12a 100644 --- a/addons/point_of_sale/static/src/js/Gui.js +++ b/addons/point_of_sale/static/src/js/Gui.js @@ -1,31 +1,7 @@ /** @odoo-module */ const { status } = owl; -/** - * This module bridges the data classes (such as those defined in - * models.js) to the view (Component) but not vice versa. - * - * The idea is to be able to perform side-effects to the user interface - * during calculation. Think of console.log during times we want to see - * the result of calculations. This is no different, except that instead - * of printing something in the console, we access a method in the user - * interface then the user interface reacts, e.g. calling `showPopup`. - * - * This however can be dangerous to the user interface as it can be possible - * that a rendered component is destroyed during the calculation. Because of - * this, we are going to limit external ui controls to those safe ones to - * use such as: - * - `showPopup` - * - `showTempScreen` - * - * IMPROVEMENT: After all, this Gui layer seems to be a good abstraction because - * there is a complete decoupling between data and view despite the data being - * able to use selected functionalities in the view layer. More formalized - * implementation is welcome. - */ - const config = {}; - /** * Call this when the user interface is ready. Provide the component * that will be used to control the ui. @@ -33,21 +9,11 @@ const config = {}; */ export const configureGui = ({ component }) => { config.component = component; - config.availableMethods = new Set([ - "showScreen", - "showPopup", - "showTempScreen", - "playSound", - "setSyncStatus", - "showNotification", - ]); + config.availableMethods = new Set(["setSyncStatus"]); }; export class GuiNotReadyError extends Error {} -/** - * Import this and consume like so: `Gui.showPopup(, )`. - * Like you would call `showPopup` in a component. - */ +// FIXME POSREF: remove this entirely. export const Gui = new Proxy(config, { get(target, key) { const { component, availableMethods } = target; diff --git a/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js b/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js index 3e6cf95e2e5..f0c92eac27e 100644 --- a/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js +++ b/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js @@ -1,6 +1,7 @@ /** @odoo-module */ import { PosComponent } from "@point_of_sale/js/PosComponent"; +import { useService } from "@web/core/utils/hooks"; import { ConfirmPopup } from "../Popups/ConfirmPopup"; import { ErrorPopup } from "../Popups/ErrorPopup"; @@ -16,6 +17,7 @@ export class AbstractReceiptScreen extends PosComponent { setup() { super.setup(); this.orderReceipt = useRef("order-receipt"); + this.popup = useService("popup"); } async _printReceipt() { if (this.env.proxy.printer) { @@ -25,7 +27,7 @@ export class AbstractReceiptScreen extends PosComponent { if (printResult.successful) { return true; } else { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: printResult.message.title, body: "Do you want to print using the web printer?", }); @@ -46,7 +48,7 @@ export class AbstractReceiptScreen extends PosComponent { window.print(); return true; } catch { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: this.env._t("Printing is not supported on some browsers"), body: this.env._t( "Printing is not supported on some browsers due to no default printing protocol " + diff --git a/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js b/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js index c579be99371..a63cd24edf1 100644 --- a/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js +++ b/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js @@ -1,11 +1,17 @@ /** @odoo-module */ +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; export class IndependentToOrderScreen extends PosComponent { + static storeOnOrder = false; + setup() { + super.setup(...arguments); + this.pos = usePos(); + } close() { const order = this.env.pos.get_order(); const { name: screenName } = order.get_screen_data(); - this.showScreen(screenName); + this.pos.showScreen(screenName); } } diff --git a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js index ed252d67a79..9881fc1e2ea 100644 --- a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js +++ b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js @@ -1,7 +1,7 @@ /** @odoo-module */ +import { useExternalListener } from "@odoo/owl"; import { PosComponent } from "@point_of_sale/js/PosComponent"; -import { useBus } from "@web/core/utils/hooks"; /** * Implement this abstract class by extending it like so: @@ -22,7 +22,7 @@ import { useBus } from "@web/core/utils/hooks"; * The concrete popup can now be instantiated and be awaited for * the user's response like so: * ```js - * const { confirmed, payload } = await this.showPopup(ConcretePopup); + * const { confirmed, payload } = await this.popup.add(ConcretePopup); * // based on the implementation above, * // if confirmed, payload = 'result' * // otherwise, payload = null @@ -30,25 +30,24 @@ import { useBus } from "@web/core/utils/hooks"; */ export class AbstractAwaitablePopup extends PosComponent { setup() { - super.setup(); - if (this.props.confirmKey) { - useBus(this.env.posbus, `confirm-popup-${this.props.id}`, this.confirm); + super.setup(...arguments); + useExternalListener(window, "keyup", this._onWindowKeyup); + } + _onWindowKeyup(event) { + if (!this.props.isActive || ["INPUT", "TEXTAREA"].includes(event.target.tagName)) { + return; } - if (this.props.cancelKey) { - useBus(this.env.posbus, `cancel-popup-${this.props.id}`, this.cancel); + if (event.key === this.props.cancelKey) { + this.cancel(); + } else if (event.key === this.props.confirmKey) { + this.confirm(); } } async confirm() { - this.env.posbus.trigger("close-popup", { - popupId: this.props.id, - response: { confirmed: true, payload: await this.getPayload() }, - }); + this.props.close({ confirmed: true, payload: await this.getPayload() }); } cancel() { - this.env.posbus.trigger("close-popup", { - popupId: this.props.id, - response: { confirmed: false, payload: null }, - }); + this.props.close({ confirmed: false, payload: null }); } /** * Override this in the concrete popup implementation to set the diff --git a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js index cfdf767b798..c96f4489f7e 100644 --- a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js @@ -1,6 +1,7 @@ /** @odoo-module */ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; +import { useService } from "@web/core/utils/hooks"; import { MoneyDetailsPopup } from "./MoneyDetailsPopup"; const { useState } = owl; @@ -17,6 +18,7 @@ export class CashOpeningPopup extends AbstractAwaitablePopup { notes: "", openingCash: this.env.pos.pos_session.cash_register_balance_start || 0, }); + this.popup = useService("popup"); } //@override async confirm() { @@ -30,7 +32,7 @@ export class CashOpeningPopup extends AbstractAwaitablePopup { super.confirm(); } async openDetailsPopup() { - const { confirmed, payload } = await this.showPopup(MoneyDetailsPopup, { + const { confirmed, payload } = await this.popup.add(MoneyDetailsPopup, { moneyDetails: this.moneyDetails, total: this.manualInputCashCount ? 0 : this.state.openingCash, }); diff --git a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js index 68fdc6a2988..e5cae0cb6cf 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -7,6 +7,8 @@ import { SaleDetailsButton } from "../ChromeWidgets/SaleDetailsButton"; import { ConfirmPopup } from "./ConfirmPopup"; import { ErrorPopup } from "./ErrorPopup"; import { MoneyDetailsPopup } from "./MoneyDetailsPopup"; +import { AlertPopup } from "./AlertPopup"; +import { useService } from "@web/core/utils/hooks"; const { useState } = owl; @@ -16,6 +18,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup { setup() { super.setup(); + this.popup = useService("popup"); this.manualInputCashCount = false; this.cashControl = this.env.pos.config.cash_control; this.closeSessionClicked = false; @@ -31,7 +34,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup { if (!this.cashControl || !this.hasDifference()) { this.closeSession(); } else if (this.hasUserAuthority()) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Payments Difference"), body: this.env._t( "Do you want to accept payments difference and post a profit/loss journal entry?" @@ -41,7 +44,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup { this.closeSession(); } } else { - await this.showPopup(ConfirmPopup, { + await this.popup.add(ConfirmPopup, { title: this.env._t("Payments Difference"), body: _.str.sprintf( this.env._t( @@ -61,7 +64,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup { } } async openDetailsPopup() { - const { confirmed, payload } = await this.showPopup(MoneyDetailsPopup, { + const { confirmed, payload } = await this.popup.add(MoneyDetailsPopup, { moneyDetails: this.moneyDetails, total: this.manualInputCashCount ? 0 @@ -182,7 +185,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup { throw error; } else { // FIXME POSREF: why are we catching errors here but not anywhere else in this method? - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: this.env._t("Closing session error"), body: this.env._t( "An error has occurred when trying to close the session.\n" + @@ -201,14 +204,14 @@ export class ClosePosPopup extends AbstractAwaitablePopup { let title = ""; if (response.type == "alert") { - popupType = "AlertPopup"; + popupType = AlertPopup; title = response.title ? response.title : ""; } else { - popupType = "ErrorPopup"; + popupType = ErrorPopup; title = "Error"; } - await this.showPopup(popupType, { title: title, body: response.message }); + await this.popup.add(popupType, { title: title, body: response.message }); if (response.redirect) { window.location = "/web#action=point_of_sale.action_client_pos_menu"; } diff --git a/addons/point_of_sale/static/src/js/Popups/EditListPopup.js b/addons/point_of_sale/static/src/js/Popups/EditListPopup.js index 7f0f042e70c..7b36bd90375 100644 --- a/addons/point_of_sale/static/src/js/Popups/EditListPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/EditListPopup.js @@ -26,7 +26,7 @@ const { useState } = owl; * * // supply the items to the popup and wait for user's response * // when user pressed `confirm` in the popup, the changes he made will be returned by the showPopup function. - * const { confirmed, payload: newNames } = await this.showPopup(EditListPopup, { + * const { confirmed, payload: newNames } = await this.popup.add(EditListPopup, { * title: "Can you confirm this item?", * array: names }) * diff --git a/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js b/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js index 00c3cca7d16..3b0d930a21f 100644 --- a/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js @@ -2,6 +2,7 @@ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { _lt } from "@web/core/l10n/translation"; +import { useService } from "@web/core/utils/hooks"; // formerly ErrorPopupWidget export class ErrorPopup extends AbstractAwaitablePopup { @@ -16,8 +17,9 @@ export class ErrorPopup extends AbstractAwaitablePopup { setup() { super.setup(); owl.onMounted(this.onMounted); + this.sound = useService("sound"); } onMounted() { - this.playSound("error"); + this.sound.play("error"); } } diff --git a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js index 5bd0ee9824c..f71662d9285 100644 --- a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js @@ -3,8 +3,7 @@ import core from "web.core"; var _t = core._t; import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { Draggable } from "../Misc/Draggable"; @@ -44,7 +43,8 @@ export class NumberPopup extends AbstractAwaitablePopup { .replace(".", this.decimalSeparator); } this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected }); - numberBuffer.use({ + this.numberBuffer = useService("number_buffer"); + this.numberBuffer.use({ nonKeyboardInputEvent: "numpad-click-input", triggerAtEnter: "accept-input", triggerAtEscape: "close-this-popup", @@ -65,7 +65,7 @@ export class NumberPopup extends AbstractAwaitablePopup { } } confirm(event) { - if (numberBuffer.get()) { + if (this.numberBuffer.get()) { super.confirm(); } } @@ -73,6 +73,6 @@ export class NumberPopup extends AbstractAwaitablePopup { this.trigger("numpad-click-input", { key }); } getPayload() { - return numberBuffer.get(); + return this.numberBuffer.get(); } } diff --git a/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js b/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js index 0f3d9bcb169..3370d434484 100644 --- a/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js @@ -19,6 +19,12 @@ export class OfflineErrorPopup extends ErrorPopup { title: _lt("Offline Error"), body: _lt("Either the server is inaccessible or browser is not connected online."), }; + setup() { + super.setup(...arguments); + if (this.constructor.dontShow) { + this.cancel(); + } + } dontShowAgain() { this.constructor.dontShow = true; diff --git a/addons/point_of_sale/static/src/js/Popups/PosPopupController.js b/addons/point_of_sale/static/src/js/Popups/PosPopupController.js deleted file mode 100644 index 70e76618f5a..00000000000 --- a/addons/point_of_sale/static/src/js/Popups/PosPopupController.js +++ /dev/null @@ -1,122 +0,0 @@ -/** @odoo-module */ - -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 - * by coordinating with them thru the `env.posbus`. The basic steps follow: - * 1. `showPopup` method triggers `show-popup` event resulting to the - * mounting of the requested popup. - * 2. When the popup is shown, the `confirm`/`cancel` method of the popup - * will be called after the popup is used. `confirming`/`cancelling` - * will trigger the `close-popup`, which this component also listens to, - * resulting to closing of the popup. - * - * Furthermore, Pressing `confirmKey`/`cancelKey` which defaults to - * 'Enter'/'Escape', will automatically `confirm`/`cancel` the `topPopup`. - * This behavior is accomplished by listening to `keyup` event of the window. - * When the `confirmKey`/`cancelKey` of the `topPopup` is pressed, - * 'cancel-popup-{top-popup-id}'/'confirm-popup-{top-popup-id}' will be triggered - * and since the popup is listening to that event (@see AbstractAwaitablePopup), - * it will result to the call of `confirm`/`cancel` method. - * - * @typedef {{ id: number, resolve: Function, keepBehind?: boolean, cancelKey?: string, confirmKey?: string }} BasePopupProps - * @typedef {{ name: string, component: AbstractAwaitablePopup, props: BasePopupProps, key: string }} Popup - */ -export class PosPopupController extends PosComponent { - static template = "point_of_sale.PosPopupController"; - 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); - useExternalListener(window, "keyup", this._onWindowKeyup); - this.popups = useState([]); - } - /** - * A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default. - * Also, if it's not the top popup, it is hidden from the view. - * This can be overridden by the default props of the popop component - * and the props used in requesting to show the popup. - * - * @param {AbstractAwaitablePopup} popupComponent - * @param {Object} props - * @returns {BasePopupProps} - */ - _showPopup(event) { - let { id, component, props, resolve } = event.detail; - props = Object.assign(props || {}, { id, resolve }); - if (component.dontShow) { - resolve(); - return; - } - this.popups.push({ - component, - props: { - keepBehind: false, - cancelKey: "Escape", - confirmKey: "Enter", - // FIXME POSREF assigning the default props by hand defeats the point of default props - ...component.defaultProps, - ...props, - }, - }); - } - _closePopup(event) { - const { popupId, response } = event.detail; - const index = this.popups.findIndex((popup) => popup.props.id == popupId); - if (index != -1) { - const popup = this.popups[index]; - popup.props.resolve(response); - this.popups.splice(index, 1); - } - } - _onWindowKeyup(event) { - const eventIsFromInputField = - event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA"; - const shouldHandleKey = this.topPopup && !eventIsFromInputField; - if (!shouldHandleKey) { - return; - } - - if (event.key === this.topPopup.props.cancelKey) { - this.env.posbus.trigger(`cancel-popup-${this.topPopup.props.id}`); - } else if (event.key === this.topPopup.props.confirmKey) { - this.env.posbus.trigger(`confirm-popup-${this.topPopup.props.id}`); - } - } - /** - * @returns {boolean} Hide the element of this component when this returns false. - */ - isShown() { - return this.popups.length > 0; - } - get topPopup() { - return this.popups[this.popups.length - 1]; - } - /** - * By default, only show the top popup. But always show a popup if - * `keepBehind` props is true. Meaning, if you have 2 popups, and - * the bottom popup has `keepBehind = true`, then the bottom popup - * will be visible if it's not blocked in the view by the top popup. - * - * @param {Popup} popup - * @returns {boolean} - */ - shouldShow(popup) { - 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 8d0af987c86..b250a72404e 100644 --- a/addons/point_of_sale/static/src/js/PosComponent.js +++ b/addons/point_of_sale/static/src/js/PosComponent.js @@ -1,54 +1,9 @@ /** @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 - * returns a response after user interaction. See the following for quick - * demonstration: - * - * ``` - * async getUserName() { - * const userResponse = await this.showPopup(TextInputPopup, { title: 'What is your name?' }); - * // at this point, the TextInputPopup is displayed. Depending on how the popup is defined, - * // say the input contains the name, the result of the interaction with the user is - * // saved in `userResponse`. - * console.log(userResponse); // logs { confirmed: true, payload: } - * } - * ``` - * - * @param {String} name Name of the popup component - * @param {Object} props Object that will be used to render to popup - */ - showPopup(component, props) { - return new Promise((resolve) => { - this.env.posbus.trigger("show-popup", { component, props, resolve, id: nextId++ }); - }); - } - showTempScreen(name, props) { - return new Promise((resolve) => { - this.trigger("show-temp-screen", { name, props, resolve }); - }); - } - showScreen(name, props) { - this.trigger("show-main-screen", { name, props }); - } - /** - * @param {String} name 'bell' | 'error' - */ - playSound(name) { - this.sound.play(name); - } /** * Control the SyncNotification component. * @param {String} status 'connected' | 'connecting' | 'disconnected' | 'error' @@ -57,7 +12,4 @@ export class PosComponent extends LegacyComponent { setSyncStatus(status, pending) { this.trigger("set-sync-status", { status, pending }); } - showNotification(message, duration) { - this.notification.add(message, duration); - } } diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js index bd8e78e5ddf..0ca6080235d 100644 --- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js +++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js @@ -4,6 +4,7 @@ import { _t } from "web.core"; import { getDataURLFromFile } from "web.utils"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { useService } from "@web/core/utils/hooks"; const { onMounted, onWillUnmount } = owl; @@ -12,6 +13,7 @@ export class PartnerDetailsEdit extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); this.intFields = ["country_id", "state_id", "property_product_pricelist"]; const partner = this.props.partner; this.changes = { @@ -66,7 +68,7 @@ export class PartnerDetailsEdit extends PosComponent { } } if ((!this.props.partner.name && !processedChanges.name) || processedChanges.name === "") { - return this.showPopup(ErrorPopup, { + return this.popup.add(ErrorPopup, { title: _t("A Customer Name Is Required"), }); } @@ -76,7 +78,7 @@ export class PartnerDetailsEdit extends PosComponent { async uploadImage(event) { const file = event.target.files[0]; if (!file.type.match(/image.*/)) { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: this.env._t("Unsupported File Format"), body: this.env._t( "Only web-compatible Image formats such as .png or .jpeg are supported." @@ -124,7 +126,7 @@ export class PartnerDetailsEdit extends PosComponent { const img = new Image(); img.addEventListener("load", () => resolve(img)); img.addEventListener("error", () => { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Loading Image Error"), body: this.env._t("Encountered error when loading image. Please try again."), }); diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js index 9e282d64504..49bd7f5e52e 100644 --- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js @@ -4,10 +4,11 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { registry } from "@web/core/registry"; import { debounce } from "@web/core/utils/timing"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { PartnerLine } from "./PartnerLine"; import { PartnerDetailsEdit } from "./PartnerDetailsEdit"; +import { usePos } from "@point_of_sale/app/pos_hook"; const { onWillUnmount, useRef } = owl; @@ -32,6 +33,8 @@ export class PartnerListScreen extends PosComponent { setup() { super.setup(); + this.pos = usePos(); + this.notification = useService("pos_notification"); useListener("click-save", () => this.env.bus.trigger("save-partner")); useListener("save-changes", this.saveChanges); this.searchWordInputRef = useRef("search-word-input-partner"); @@ -59,12 +62,12 @@ export class PartnerListScreen extends PosComponent { this.render(true); } else { this.props.resolve({ confirmed: false, payload: false }); - this.trigger("close-temp-screen"); + this.pos.closeTempScreen(); } } confirm() { this.props.resolve({ confirmed: true, payload: this.state.selectedPartner }); - this.trigger("close-temp-screen"); + this.pos.closeTempScreen(); } activateEditMode() { this.state.detailIsShown = true; @@ -112,7 +115,7 @@ export class PartnerListScreen extends PosComponent { return; } const result = await this.searchPartner(); - this.showNotification( + this.notification.add( _.str.sprintf( this.env._t('%s customer(s) found for "%s".'), result.length, diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index 0632c00c0c9..fce500da05a 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -3,9 +3,8 @@ import { parse } from "web.field_utils"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { useErrorHandlers } from "@point_of_sale/js/custom_hooks"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; import { registry } from "@web/core/registry"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import utils from "web.utils"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; @@ -16,6 +15,7 @@ import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; import { PaymentScreenNumpad } from "./PaymentScreenNumpad"; import { PaymentScreenPaymentLines } from "./PaymentScreenPaymentLines"; import { PaymentScreenStatus } from "./PaymentScreenStatus"; +import { usePos } from "@point_of_sale/app/pos_hook"; export class PaymentScreen extends PosComponent { static template = "PaymentScreen"; @@ -27,6 +27,9 @@ export class PaymentScreen extends PosComponent { setup() { super.setup(); + this.pos = usePos(); + this.popup = useService("popup"); + this.notification = useService("pos_notification"); useListener("delete-payment-line", this.deletePaymentLine); useListener("select-payment-line", this.selectPaymentLine); useListener("new-payment-line", this.addNewPaymentLine); @@ -39,14 +42,15 @@ export class PaymentScreen extends PosComponent { this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) => this.env.pos.config.payment_method_ids.includes(method.id) ); - numberBuffer.use(this._getNumberBufferConfig); + this.numberBuffer = useService("number_buffer"); + this.numberBuffer.use(this._getNumberBufferConfig); useErrorHandlers(); this.payment_interface = null; this.error = false; } showMaxValueError() { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Maximum value reached"), body: this.env._t( "The amount cannot be higher than the due amount if you don't have a cash payment method configured." @@ -87,9 +91,12 @@ export class PaymentScreen extends PosComponent { // IMPROVEMENT: This code snippet is repeated multiple times. // Maybe it's better to create a function for it. const currentPartner = this.currentOrder.get_partner(); - const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", { - partner: currentPartner, - }); + const { confirmed, payload: newPartner } = await this.pos.showTempScreen( + "PartnerListScreen", + { + partner: currentPartner, + } + ); if (confirmed) { this.currentOrder.set_partner(newPartner); this.currentOrder.updatePricelist(newPartner); @@ -99,10 +106,10 @@ export class PaymentScreen extends PosComponent { // original function: click_paymentmethods const result = this.currentOrder.add_paymentline(paymentMethod); if (result) { - numberBuffer.reset(); + this.numberBuffer.reset(); return true; } else { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Error"), body: this.env._t("There is already an electronic payment in progress."), }); @@ -124,10 +131,10 @@ export class PaymentScreen extends PosComponent { ) { return; } - if (numberBuffer.get() === null) { + if (this.numberBuffer.get() === null) { this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } }); } else { - this.selectedPaymentLine.set_amount(numberBuffer.getFloat()); + this.selectedPaymentLine.set_amount(this.numberBuffer.getFloat()); } } toggleIsToInvoice() { @@ -144,7 +151,7 @@ export class PaymentScreen extends PosComponent { const change = this.currentOrder.get_change(); const value = tip === 0 && change > 0 ? change : tip; - const { confirmed, payload } = await this.showPopup(NumberPopup, { + const { confirmed, payload } = await this.popup.add(NumberPopup, { title: tip ? this.env._t("Change Tip") : this.env._t("Add Tip"), startingValue: value, isInputSelected: true, @@ -156,7 +163,7 @@ export class PaymentScreen extends PosComponent { } async toggleShippingDatePicker() { if (!this.currentOrder.getShippingDate()) { - const { confirmed, payload: shippingDate } = await this.showPopup(DatePickerPopup, { + const { confirmed, payload: shippingDate } = await this.popup.add(DatePickerPopup, { title: this.env._t("Select the shipping date"), }); if (confirmed) { @@ -180,12 +187,12 @@ export class PaymentScreen extends PosComponent { .send_payment_cancel(this.currentOrder, cid) .then(function () { self.currentOrder.remove_paymentline(line); - numberBuffer.reset(); + this.numberBuffer.reset(); self.render(true); }); } else if (line.get_payment_status() !== "waitingCancel") { this.currentOrder.remove_paymentline(line); - numberBuffer.reset(); + this.numberBuffer.reset(); this.render(true); } } @@ -193,13 +200,13 @@ export class PaymentScreen extends PosComponent { const { cid } = event.detail; const line = this.paymentLines.find((line) => line.cid === cid); this.currentOrder.select_paymentline(line); - numberBuffer.reset(); + this.numberBuffer.reset(); this.render(true); } async validateOrder(isForceValidate) { if (this.env.pos.config.cash_rounding) { if (!this.env.pos.get_order().check_paymentlines_rounding()) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Rounding error in payment lines"), body: this.env._t( "The amount of your payment lines must be rounded to validate the transaction." @@ -259,7 +266,7 @@ export class PaymentScreen extends PosComponent { syncOrderResult.map((res) => res.id) ); if (!postPushResult) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Error: no internet connection."), body: this.env._t( "Some, if not all, post-processing after syncing order failed." @@ -284,14 +291,14 @@ export class PaymentScreen extends PosComponent { } finally { // Always show the next screen regardless of error since pos has to // continue working even offline. - this.showScreen(this.nextScreen); + this.pos.showScreen(this.nextScreen); // Remove the order from the local storage so that when we refresh the page, the order // won't be there this.env.pos.db.remove_unpaid_order(this.currentOrder); // Ask the user to sync the remaining unsynced orders. if (!hasError && syncOrderResult && this.env.pos.db.get_orders().length) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Remaining unsynced orders"), body: this.env._t( "There are unsynced orders. Do you want to sync these orders?" @@ -322,7 +329,7 @@ export class PaymentScreen extends PosComponent { } async _isOrderValid(isForceValidate) { if (this.currentOrder.get_orderlines().length === 0 && this.currentOrder.is_to_invoice()) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Empty Order"), body: this.env._t( "There must be at least one product in your order before it can be validated and invoiced." @@ -336,7 +343,7 @@ export class PaymentScreen extends PosComponent { ); if (splitPayments.length && !this.currentOrder.get_partner()) { const paymentMethod = splitPayments[0].payment_method; - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Customer Required"), body: _.str.sprintf( this.env._t("Customer is required for %s payment method."), @@ -353,7 +360,7 @@ export class PaymentScreen extends PosComponent { (this.currentOrder.is_to_invoice() || this.currentOrder.getShippingDate()) && !this.currentOrder.get_partner() ) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Please select the Customer"), body: this.env._t( "You need to select the customer before you can invoice or ship an order." @@ -370,7 +377,7 @@ export class PaymentScreen extends PosComponent { this.currentOrder.getShippingDate() && !(partner.name && partner.street && partner.city && partner.country_id) ) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Incorrect address for shipping"), body: this.env._t("The selected customer needs an address."), }); @@ -381,7 +388,7 @@ export class PaymentScreen extends PosComponent { this.currentOrder.get_total_with_tax() != 0 && this.currentOrder.get_paymentlines().length === 0 ) { - this.showNotification(this.env._t("Select a payment method to validate the order.")); + this.notification.add(this.env._t("Select a payment method to validate the order.")); return false; } @@ -391,7 +398,7 @@ export class PaymentScreen extends PosComponent { if (this.currentOrder.has_not_valid_rounding()) { var line = this.currentOrder.has_not_valid_rounding(); - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Incorrect rounding"), body: this.env._t( "You have to round your payments lines." + line.amount + " is not rounded." @@ -413,7 +420,7 @@ export class PaymentScreen extends PosComponent { cash = cash || this.env.pos.payment_methods[i].is_cash_count; } if (!cash) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Cannot return change without a cash payment method"), body: this.env._t( "There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration" @@ -429,23 +436,25 @@ export class PaymentScreen extends PosComponent { this.currentOrder.get_total_with_tax() > 0 && this.currentOrder.get_total_with_tax() * 1000 < this.currentOrder.get_total_paid() ) { - this.showPopup(ConfirmPopup, { - title: this.env._t("Please Confirm Large Amount"), - body: - this.env._t("Are you sure that the customer wants to pay") + - " " + - this.env.pos.format_currency(this.currentOrder.get_total_paid()) + - " " + - this.env._t("for an order of") + - " " + - this.env.pos.format_currency(this.currentOrder.get_total_with_tax()) + - " " + - this.env._t('? Clicking "Confirm" will validate the payment.'), - }).then(({ confirmed }) => { - if (confirmed) { - this.validateOrder(true); - } - }); + this.popup + .add(ConfirmPopup, { + title: this.env._t("Please Confirm Large Amount"), + body: + this.env._t("Are you sure that the customer wants to pay") + + " " + + this.env.pos.format_currency(this.currentOrder.get_total_paid()) + + " " + + this.env._t("for an order of") + + " " + + this.env.pos.format_currency(this.currentOrder.get_total_with_tax()) + + " " + + this.env._t('? Clicking "Confirm" will validate the payment.'), + }) + .then(({ confirmed }) => { + if (confirmed) { + this.validateOrder(true); + } + }); return false; } diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js index c1345e4a7d9..5e2795160c2 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { TextAreaPopup } from "@point_of_sale/js/Popups/TextAreaPopup"; export class OrderlineCustomerNoteButton extends PosComponent { @@ -10,6 +10,7 @@ export class OrderlineCustomerNoteButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("click", this.onClick); } async onClick() { @@ -17,8 +18,7 @@ export class OrderlineCustomerNoteButton extends PosComponent { if (!selectedOrderline) { return; } - - const { confirmed, payload: inputNote } = await this.showPopup(TextAreaPopup, { + const { confirmed, payload: inputNote } = await this.popup.add(TextAreaPopup, { startingValue: selectedOrderline.get_customer_note(), title: this.env._t("Add Customer Note"), }); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js index 091ed2fd88f..8c0374907d9 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { ProductInfoPopup } from "@point_of_sale/js/Popups/ProductInfoPopup"; export class ProductInfoButton extends PosComponent { @@ -11,6 +11,7 @@ export class ProductInfoButton extends PosComponent { setup() { super.setup(); useListener("click", this.onClick); + this.popup = useService("popup"); } async onClick() { const orderline = this.env.pos.get_order().get_selected_orderline(); @@ -18,7 +19,7 @@ export class ProductInfoButton extends PosComponent { const product = orderline.get_product(); const quantity = orderline.get_quantity(); const info = await this.env.pos.getProductInfo(product, quantity); - this.showPopup(ProductInfoPopup, { info: info, product: product }); + this.popup.add(ProductInfoPopup, { info: info, product: product }); } } } diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js index 56019ebc476..8ff83929473 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js @@ -1,5 +1,6 @@ /** @odoo-module */ +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { useListener } from "@web/core/utils/hooks"; @@ -9,12 +10,13 @@ export class RefundButton extends PosComponent { setup() { super.setup(); + this.pos = usePos(); useListener("click", this._onClick); } _onClick() { const partner = this.env.pos.get_order().get_partner(); const searchDetails = partner ? { fieldName: "PARTNER", searchTerm: partner.name } : {}; - this.showScreen("TicketScreen", { + this.pos.showScreen("TicketScreen", { ui: { filter: "SYNCED", searchDetails }, destinationOrder: this.env.pos.get_order(), }); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js index 0ee0b354ee4..08e8a75e094 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; export class SetFiscalPositionButton extends PosComponent { @@ -10,6 +10,7 @@ export class SetFiscalPositionButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("click", this.onClick); } get currentOrder() { @@ -39,7 +40,7 @@ export class SetFiscalPositionButton extends PosComponent { item: fiscalPos, }); } - const { confirmed, payload: selectedFiscalPosition } = await this.showPopup( + const { confirmed, payload: selectedFiscalPosition } = await this.popup.add( SelectionPopup, { title: this.env._t("Select Fiscal Position"), diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js index 7473d55a0e8..9b645759d3f 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; export class SetPricelistButton extends PosComponent { @@ -10,6 +10,7 @@ export class SetPricelistButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("click", this.onClick); } get currentOrder() { @@ -40,7 +41,7 @@ export class SetPricelistButton extends PosComponent { }); } - const { confirmed, payload: selectedPricelist } = await this.showPopup(SelectionPopup, { + const { confirmed, payload: selectedPricelist } = await this.popup.add(SelectionPopup, { title: this.env._t("Select the pricelist"), list: selectionList, }); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js index 46feb8667f5..0cbc3792d8f 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup"; @@ -15,13 +15,16 @@ export class OrderWidget extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("select-line", this._selectLine); useListener("edit-pack-lot-lines", this._editPackLotLines); this.scrollableRef = useRef("scrollable"); useEffect( () => { - const selectedLineEl = this.scrollableRef.el && this.scrollableRef.el.querySelector(".orderline.selected"); - if(selectedLineEl) { + const selectedLineEl = + this.scrollableRef.el && + this.scrollableRef.el.querySelector(".orderline.selected"); + if (selectedLineEl) { selectedLineEl.scrollIntoView({ behavior: "smooth", block: "start" }); } }, @@ -48,7 +51,7 @@ export class OrderWidget extends PosComponent { const orderline = event.detail.orderline; const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot(); const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot); - const { confirmed, payload } = await this.showPopup(EditListPopup, { + const { confirmed, payload } = await this.popup.add(EditListPopup, { title: this.env._t("Lot/Serial Number(s) Required"), name: orderline.product.display_name, isSingleItem: isAllowOnlyOneLot, diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js index 20f1a85f680..302d5029db6 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js @@ -2,9 +2,8 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ControlButtonsMixin } from "@point_of_sale/js/ControlButtonsMixin"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; import { registry } from "@web/core/registry"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { useBarcodeReader } from "@point_of_sale/js/custom_hooks"; import { parse } from "web.field_utils"; @@ -21,6 +20,7 @@ import { MobileOrderWidget } from "../../Misc/MobileOrderWidget"; import { NumpadWidget } from "./NumpadWidget"; import { OrderWidget } from "./OrderWidget"; import { ProductsWidget } from "./ProductsWidget"; +import { usePos } from "@point_of_sale/app/pos_hook"; const { onMounted, useState } = owl; @@ -36,6 +36,8 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { setup() { super.setup(); + this.pos = usePos(); + this.popup = useService("popup"); useListener("update-selected-orderline", this._updateSelectedOrderline); useListener("select-line", this._selectLine); useListener("set-numpad-mode", this._setNumpadMode); @@ -50,7 +52,8 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { discount: this._barcodeDiscountAction, error: this._barcodeErrorAction, }); - numberBuffer.use({ + this.numberBuffer = useService("number_buffer"); + this.numberBuffer.use({ nonKeyboardInputEvent: "numpad-click-input", triggerAtInput: "update-selected-orderline", useWithBarcode: true, @@ -59,7 +62,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { // Call `reset` when the `onMounted` callback in `numberBuffer.use` is done. // We don't do this in the `mounted` lifecycle method because it is called before // the callbacks in `onMounted` hook. - onMounted(() => numberBuffer.reset()); + onMounted(() => this.numberBuffer.reset()); this.state = useState({ mobile_pane: this.props.mobile_pane || "right", }); @@ -90,7 +93,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { product.attribute_line_ids, (id) => this.env.pos.attributes_by_ptal_id[id] ).filter((attr) => attr !== undefined); - const { confirmed, payload } = await this.showPopup(ProductConfiguratorPopup, { + const { confirmed, payload } = await this.popup.add(ProductConfiguratorPopup, { product: product, attributes: attributes, }); @@ -123,7 +126,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { packLotLinesToEdit = []; } } - const { confirmed, payload } = await this.showPopup(EditListPopup, { + const { confirmed, payload } = await this.popup.add(EditListPopup, { title: this.env._t("Lot/Serial Number(s) Required"), name: product.display_name, isSingleItem: isAllowOnlyOneLot, @@ -149,7 +152,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { if (product.to_weight && this.env.pos.config.iface_electronic_scale) { // Show the ScaleScreen to weigh the product. if (this.isScaleAvailable) { - const { confirmed, payload } = await this.showTempScreen("ScaleScreen", { + const { confirmed, payload } = await this.pos.showTempScreen("ScaleScreen", { product, }); if (confirmed) { @@ -184,16 +187,16 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { } // Add the product after having the extra information. await this._addProduct(product, options); - numberBuffer.reset(); + this.numberBuffer.reset(); } _setNumpadMode(event) { const { mode } = event.detail; - numberBuffer.capture(); - numberBuffer.reset(); + this.numberBuffer.capture(); + this.numberBuffer.reset(); this.env.pos.numpadMode = mode; } _selectLine() { - numberBuffer.reset(); + this.numberBuffer.reset(); } async _updateSelectedOrderline(event) { if (this.env.pos.numpadMode === "quantity" && this.env.pos.disallowLineQuantityChange()) { @@ -210,7 +213,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { .get_quantity(); if (selectedLine.noDecrease) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Invalid action"), body: this.env._t("You are not allowed to change this quantity"), }); @@ -229,7 +232,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { const val = buffer === null ? "remove" : buffer; this._setValue(val); if (val == "remove") { - numberBuffer.reset(); + this.numberBuffer.reset(); this.env.pos.numpadMode = "quantity"; } } @@ -239,7 +242,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { if (this.env.pos.numpadMode === "quantity") { const result = this.currentOrder.get_selected_orderline().set_quantity(val); if (!result) { - numberBuffer.reset(); + this.numberBuffer.reset(); } } else if (this.env.pos.numpadMode === "discount") { this.currentOrder.get_selected_orderline().set_discount(val); @@ -319,7 +322,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { // Why? Because once we start declaring barcode actions in different // screens, these methods will also be declared over and over. _barcodeErrorAction(code) { - this.showPopup(ErrorBarcodePopup, { code: this._codeRepr(code) }); + this.popup.add(ErrorBarcodePopup, { code: this._codeRepr(code) }); } _codeRepr(code) { if (code.code.length > 32) { @@ -329,7 +332,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { } } async _displayAllControlPopup() { - await this.showPopup(ControlButtonPopup, { + await this.popup.add(ControlButtonPopup, { controlButtons: this.controlButtons, }); } @@ -339,7 +342,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { */ async _onScaleNotAvailable() {} async _showDecreaseQuantityPopup() { - const { confirmed, payload: inputNumber } = await this.showPopup(NumberPopup, { + const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, { startingValue: 0, title: this.env._t("Set the new quantity"), }); @@ -370,7 +373,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { // IMPROVEMENT: This code snippet is very similar to selectPartner of PaymentScreen. const currentPartner = this.currentOrder.get_partner(); if (currentPartner && this.currentOrder.getHasRefundLines()) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Can't change customer"), body: _.str.sprintf( this.env._t( @@ -381,9 +384,12 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { }); return; } - const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", { - partner: currentPartner, - }); + const { confirmed, payload: newPartner } = await this.pos.showTempScreen( + "PartnerListScreen", + { + partner: currentPartner, + } + ); if (confirmed) { this.currentOrder.set_partner(newPartner); this.currentOrder.updatePricelist(newPartner); @@ -400,7 +406,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { (this.env.pos.picking_type.use_create_lots || this.env.pos.picking_type.use_existing_lots) ) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Some Serial/Lot Numbers are missing"), body: this.env._t( "You are trying to sell products with serial/lot numbers, but some of them are not set.\nWould you like to proceed anyway?" @@ -409,10 +415,10 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) { cancelText: this.env._t("No"), }); if (confirmed) { - this.showScreen("PaymentScreen"); + this.pos.showScreen("PaymentScreen"); } } else { - this.showScreen("PaymentScreen"); + this.pos.showScreen("PaymentScreen"); } } switchPane() { diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js index e59cd92d2e7..2529028b6d5 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { useListener } from "@web/core/utils/hooks"; -import { usePos } from "@point_of_sale/app/pos_store"; +import { usePos } from "@point_of_sale/app/pos_hook"; import { ProductItem } from "./ProductItem"; import { ProductsWidgetControlPanel } from "./ProductsWidgetControlPanel"; @@ -64,10 +64,7 @@ export class ProductsWidget extends PosComponent { return this.env.pos.db.get_category_childs_ids(0).length === 0; } get shouldShowButton() { - return ( - this.productsToDisplay.length === 0 && - this.searchWord - ); + return this.productsToDisplay.length === 0 && this.searchWord; } _switchCategory(event) { this.env.pos.setSelectedCategoryId(event.detail); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js index d1717a65775..629009cae8b 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js @@ -2,11 +2,12 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { debounce } from "@web/core/utils/timing"; -import { usePos } from "@point_of_sale/app/pos_store"; +import { usePos } from "@point_of_sale/app/pos_hook"; import { CategoryButton } from "./CategoryButton"; import { onMounted, onWillUnmount, useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; export class ProductsWidgetControlPanel extends PosComponent { static components = { CategoryButton }; @@ -15,6 +16,7 @@ export class ProductsWidgetControlPanel extends PosComponent { setup() { super.setup(); this.pos = usePos(); + this.notification = useService("pos_notification"); this.updateSearch = debounce(this.updateSearch, 100); this.state = useState({ searchInput: "", mobileSearchBarIsShown: false }); @@ -50,7 +52,7 @@ export class ProductsWidgetControlPanel extends PosComponent { return; } const result = await this.loadProductFromDB(); - this.showNotification( + this.notification.add( _.str.sprintf( this.env._t('%s product(s) found for "%s".'), result.length, diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js index ea20f33efff..fb477a76362 100644 --- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js @@ -9,6 +9,7 @@ import { registry } from "@web/core/registry"; import { OrderReceipt } from "./OrderReceipt"; import { onMounted, useRef, status } from "@odoo/owl"; +import { usePos } from "@point_of_sale/app/pos_hook"; export class ReceiptScreen extends AbstractReceiptScreen { static template = "ReceiptScreen"; @@ -16,6 +17,7 @@ export class ReceiptScreen extends AbstractReceiptScreen { setup() { super.setup(); + this.pos = usePos(); useErrorHandlers(); this.orderReceipt = useRef("order-receipt"); const order = this.currentOrder; @@ -57,9 +59,7 @@ export class ReceiptScreen extends AbstractReceiptScreen { this.orderUiState.emailNotice = this.env._t("Email sent."); } catch { this.orderUiState.emailSuccessful = false; - this.orderUiState.emailNotice = this.env._t( - "Sending email failed. Please try again." - ); + this.orderUiState.emailNotice = this.env._t("Sending email failed. Please try again."); } } get orderAmountPlusTip() { @@ -104,7 +104,7 @@ export class ReceiptScreen extends AbstractReceiptScreen { this.env.pos.removeOrder(this.currentOrder); this._addNewOrder(); const { name, props } = this.nextScreen; - this.showScreen(name, props); + this.pos.showScreen(name, props); if (this.env.pos.config.iface_customer_facing_display) { this.env.pos.send_current_order_to_customer_facing_display(); } diff --git a/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js b/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js index 4404522ecb0..d5c3a96a21d 100644 --- a/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js @@ -3,6 +3,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { round_precision as round_pr } from "web.utils"; import { registry } from "@web/core/registry"; +import { usePos } from "@point_of_sale/app/pos_hook"; const { onMounted, onWillUnmount, useExternalListener, useState } = owl; @@ -19,6 +20,7 @@ export class ScaleScreen extends PosComponent { this.state = useState({ weight: 0 }); onMounted(this.onMounted); onWillUnmount(this.onWillUnmount); + this.pos = usePos(); } onMounted() { // start the scale reading @@ -30,14 +32,14 @@ export class ScaleScreen extends PosComponent { } back() { this.props.resolve({ confirmed: false, payload: null }); - this.trigger("close-temp-screen"); + this.pos.closeTempScreen(); } confirm() { this.props.resolve({ confirmed: true, payload: { weight: this.state.weight }, }); - this.trigger("close-temp-screen"); + this.pos.closeTempScreen(); } _onHotkeys(event) { if (event.key === "Escape") { diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js index 611cdb78e5d..a1e30611a60 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js @@ -1,15 +1,18 @@ /** @odoo-module */ -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; +import { usePos } from "@point_of_sale/app/pos_hook"; export class InvoiceButton extends PosComponent { static template = "InvoiceButton"; setup() { super.setup(); + this.pos = usePos(); + this.popup = useService("popup"); useListener("click", this._onClick); } get isAlreadyInvoiced() { @@ -45,7 +48,7 @@ export class InvoiceButton extends PosComponent { throw error; } else { // NOTE: error here is most probably undefined - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Network Error"), body: this.env._t("Unable to download invoice."), }); @@ -69,7 +72,7 @@ export class InvoiceButton extends PosComponent { // Part 1: Handle missing partner. // Write to pos.order the selected partner. if (!order.get_partner()) { - const { confirmed: confirmedPopup } = await this.showPopup(ConfirmPopup, { + const { confirmed: confirmedPopup } = await this.popup.add(ConfirmPopup, { title: this.env._t("Need customer to invoice"), body: this.env._t("Do you want to open the customer list to select customer?"), }); @@ -78,7 +81,7 @@ export class InvoiceButton extends PosComponent { } const { confirmed: confirmedTempScreen, payload: newPartner } = - await this.showTempScreen("PartnerListScreen"); + await this.pos.showTempScreen("PartnerListScreen"); if (!confirmedTempScreen) { return; } diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js index b5f07fa523c..b0fca023ddd 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js @@ -2,18 +2,20 @@ import { useListener } from "@web/core/utils/hooks"; import { PosComponent } from "@point_of_sale/js/PosComponent"; +import { usePos } from "@point_of_sale/app/pos_hook"; export class ReprintReceiptButton extends PosComponent { static template = "ReprintReceiptButton"; setup() { super.setup(); + this.pos = usePos(); useListener("click", this._onClick); } async _onClick() { if (!this.props.order) { return; } - this.showScreen("ReprintReceiptScreen", { order: this.props.order }); + this.pos.showScreen("ReprintReceiptScreen", { order: this.props.order }); } } diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js index 98639465775..9b3b75af340 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js @@ -1,5 +1,6 @@ /** @odoo-module */ +import { usePos } from "@point_of_sale/app/pos_hook"; import { AbstractReceiptScreen } from "@point_of_sale/js/Misc/AbstractReceiptScreen"; import { registry } from "@web/core/registry"; import { OrderReceipt } from "../ReceiptScreen/OrderReceipt"; @@ -7,21 +8,23 @@ import { OrderReceipt } from "../ReceiptScreen/OrderReceipt"; export class ReprintReceiptScreen extends AbstractReceiptScreen { static template = "ReprintReceiptScreen"; static components = { OrderReceipt }; + static storeOnOrder = false; setup() { super.setup(); + this.pos = usePos(); owl.onMounted(this.onMounted); } onMounted() { this.printReceipt(); } confirm() { - this.showScreen("TicketScreen", { reuseSavedUIState: true }); + this.pos.showScreen("TicketScreen", { reuseSavedUIState: true }); } async printReceipt() { if (this.env.proxy.printer && this.env.pos.config.iface_print_skip_screen) { const result = await this._printReceipt(); if (result) { - this.showScreen("TicketScreen", { reuseSavedUIState: true }); + this.pos.showScreen("TicketScreen", { reuseSavedUIState: true }); } } } diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js index 3374a488dda..efd51e1a1d5 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js @@ -2,9 +2,8 @@ import { Order } from "@point_of_sale/js/models"; import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; import { registry } from "@web/core/registry"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { parse } from "web.field_utils"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; @@ -16,6 +15,7 @@ import { NumpadWidget } from "../ProductScreen/NumpadWidget"; import { OrderDetails } from "../TicketScreen/OrderDetails"; import { ReprintReceiptButton } from "./ControlButtons/ReprintReceiptButton"; import { SearchBar } from "../../Misc/SearchBar"; +import { usePos } from "@point_of_sale/app/pos_hook"; const { onMounted, onWillUnmount, useState } = owl; @@ -41,6 +41,8 @@ export class TicketScreen extends IndependentToOrderScreen { setup() { super.setup(); + this.pos = usePos(); + this.popup = useService("popup"); useListener("close-screen", this._onCloseScreen); useListener("filter-selected", this._onFilterSelected); useListener("search", this._onSearch); @@ -54,7 +56,8 @@ export class TicketScreen extends IndependentToOrderScreen { useListener("click-refund-order-uid", this._onClickRefundOrderUid); useListener("update-selected-orderline", this._onUpdateSelectedOrderline); useListener("do-refund", this._onDoRefund); - numberBuffer.use({ + this.numberBuffer = useService("number_buffer"); + this.numberBuffer.use({ nonKeyboardInputEvent: "numpad-click-input", triggerAtInput: "update-selected-orderline", }); @@ -118,14 +121,14 @@ export class TicketScreen extends IndependentToOrderScreen { this._state.ui.selectedOrderlineIds[clickedOrder.backendId] = firstLine.id; } } - numberBuffer.reset(); + this.numberBuffer.reset(); } else { this._setOrder(clickedOrder); } } _onCreateNewOrder() { this.env.pos.add_new_order(); - this.showScreen("ProductScreen"); + this.pos.showScreen("ProductScreen"); } _selectNextOrder(currentOrder) { const currentOrderIndex = this._getOrderList().indexOf(currentOrder); @@ -140,7 +143,7 @@ export class TicketScreen extends IndependentToOrderScreen { ["ProductScreen", "PaymentScreen"].includes(screen.name) && order.get_orderlines().length > 0 ) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Existing orderlines"), body: _.str.sprintf( this.env._t( @@ -180,7 +183,7 @@ export class TicketScreen extends IndependentToOrderScreen { _onClickOrderline({ detail: orderline }) { const order = this.getSelectedSyncedOrder(); this._state.ui.selectedOrderlineIds[order.backendId] = orderline.id; - numberBuffer.reset(); + this.numberBuffer.reset(); } _onClickRefundOrderUid({ detail: orderUid }) { // Open the refund order. @@ -193,24 +196,24 @@ export class TicketScreen extends IndependentToOrderScreen { const buffer = detail.buffer; const order = this.getSelectedSyncedOrder(); if (!order) { - return numberBuffer.reset(); + return this.numberBuffer.reset(); } const selectedOrderlineId = this.getSelectedOrderlineId(); const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId); if (!orderline) { - return numberBuffer.reset(); + return this.numberBuffer.reset(); } const toRefundDetail = this._getToRefundDetail(orderline); // When already linked to an order, do not modify the to refund quantity. if (toRefundDetail.destinationOrderUid) { - return numberBuffer.reset(); + return this.numberBuffer.reset(); } const refundableQty = toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty; if (refundableQty <= 0) { - return numberBuffer.reset(); + return this.numberBuffer.reset(); } if (buffer == null || buffer == "") { @@ -218,8 +221,8 @@ export class TicketScreen extends IndependentToOrderScreen { } else { const quantity = Math.abs(parse.float(buffer)); if (quantity > refundableQty) { - numberBuffer.reset(); - this.showPopup(ErrorPopup, { + this.numberBuffer.reset(); + this.popup.add(ErrorPopup, { title: this.env._t("Maximum Exceeded"), body: _.str.sprintf( this.env._t( diff --git a/addons/point_of_sale/static/src/js/custom_hooks.js b/addons/point_of_sale/static/src/js/custom_hooks.js index f054e8ee61f..036c897b776 100644 --- a/addons/point_of_sale/static/src/js/custom_hooks.js +++ b/addons/point_of_sale/static/src/js/custom_hooks.js @@ -3,6 +3,7 @@ import { OfflineErrorPopup } from "./Popups/OfflineErrorPopup"; import { ConfirmPopup } from "./Popups/ConfirmPopup"; import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup"; import { ErrorPopup } from "./Popups/ErrorPopup"; +import { useEnv } from "@odoo/owl"; const { onMounted, onPatched, onWillUnmount, useComponent } = owl; @@ -11,14 +12,16 @@ const { onMounted, onPatched, onWillUnmount, useComponent } = owl; * * IMPROVEMENT: This is a terrible hook. There could be a better way to handle * the error when the order failed to sync. + * FIXME POSREF: move this to the error_handler registry. */ export function useErrorHandlers() { const component = useComponent(); + const popup = useEnv().services.popup; component._handlePushOrderError = async function (error) { // This error handler receives `error` equivalent to `error.message` of the rpc error. if (error.message === "Backend Invoice") { - await this.showPopup(ConfirmPopup, { + await popup.add(ConfirmPopup, { title: this.env._t("Please print the invoice from the backend"), body: this.env._t( @@ -31,10 +34,10 @@ export function useErrorHandlers() { const body = this.env._t( "Check the internet connection then try to sync again by clicking on the red wifi button (upper right of the screen)." ); - await this.showPopup(OfflineErrorPopup, { title, body }); + await popup.add(OfflineErrorPopup, { title, body }); } else if (error.code === 200) { // OpenERP Server Errors - await this.showPopup(ErrorTracebackPopup, { + await popup.add(ErrorTracebackPopup, { title: error.data.message || this.env._t("Server Error"), body: error.data.debug || @@ -42,7 +45,7 @@ export function useErrorHandlers() { }); } else if (error.code === 700) { // Sweden Fiscal module errors - await this.showPopup(ErrorPopup, { + await popup.add(ErrorPopup, { title: this.env._t("Fiscal data module error"), body: error.data.error.status || @@ -58,13 +61,13 @@ export function useErrorHandlers() { } else { bodyMessage = "Fiscal data module is not on."; } - await this.showPopup(ErrorPopup, { + await popup.add(ErrorPopup, { title: this.env._t("Fiscal data module error"), body: bodyMessage, }); } else { // ??? - await this.showPopup(ErrorPopup, { + await popup.add(ErrorPopup, { title: this.env._t("Unknown Error"), body: this.env._t( "The order could not be sent to the server due to an unknown error" diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index b9a5c33414e..334a2d8880e 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -7,7 +7,6 @@ import core from "web.core"; import field_utils from "web.field_utils"; import time from "web.time"; import utils from "web.utils"; -import { Gui } from "@point_of_sale/js/Gui"; import { batched, uuidv4 } from "@point_of_sale/js/utils"; import { ErrorPopup } from "./Popups/ErrorPopup"; @@ -145,6 +144,10 @@ export class PosGlobalState extends PosModel { selectedPartner: null, selectedCategoryId: null, }); + + this.ready = new Promise((resolve) => { + this.markReady = resolve; + }); } getDefaultSearchDetails() { return { @@ -167,6 +170,7 @@ export class PosGlobalState extends PosModel { await this.load_product_uom_unit(); await this.load_orders(); this.set_start_order(); + this.markReady(); } async load_server_data() { @@ -340,40 +344,37 @@ export class PosGlobalState extends PosModel { // reload the list of partner, returns as a promise that resolves if there were // updated partners, and fails if not async load_new_partners() { - let search_params = { domain: this.prepare_new_partners_domain() }; + const search_params = { domain: this.prepare_new_partners_domain() }; if (this.env.pos.config.limited_partners_loading) { - search_params['order'] = 'write_date desc'; + search_params["order"] = "write_date desc"; if (this.env.pos.config.partner_load_background) { - search_params['limit'] = this.env.pos.config.limited_partners_amount || 1; - } - else { - search_params['limit'] = 1; + search_params["limit"] = this.env.pos.config.limited_partners_amount || 1; + } else { + search_params["limit"] = 1; } } - const partners = await this.env.services - .rpc( - { - model: 'pos.session', - method: 'get_pos_ui_res_partner_by_params', - args: [[odoo.pos_session_id], search_params], - }, - { - timeout: 3000, - shadow: true, - } - ) + const partners = await this.env.services.rpc( + { + model: "pos.session", + method: "get_pos_ui_res_partner_by_params", + args: [[odoo.pos_session_id], search_params], + }, + { + timeout: 3000, + shadow: true, + } + ); if (this.env.pos.config.partner_load_background) { this.loadPartnersBackground( - search_params['domain'], + search_params["domain"], this.env.pos.config.limited_partners_amount || 1, - 'write_date desc' + "write_date desc" ); } if (this.addPartners(partners)) { - return true - } - else { - return false + return true; + } else { + return false; } } @@ -547,7 +548,7 @@ export class PosGlobalState extends PosModel { page += 1; } while (products.length == this.config.limited_products_amount); } - async loadPartnersBackground(domain=[], offset=0, order=false) { + async loadPartnersBackground(domain = [], offset = 0, order = false) { // Start at the first page since the first set of loaded partners are not actually in the // same order as this background loading procedure. let i = 0; @@ -1482,7 +1483,7 @@ export class Product extends PosModel { // product.pricelist.item records are loaded with a search_read // and were automatically sorted based on their _order by the // ORM. After that they are added in this order to the pricelists. - get_price(pricelist, quantity, price_extra, recurring=false) { + get_price(pricelist, quantity, price_extra, recurring = false) { var self = this; var date = moment(); @@ -1512,12 +1513,13 @@ export class Product extends PosModel { pricelist_items = _.filter( self.applicablePricelistItems[pricelist.id], function (item) { - return ( - (!item.categ_id || _.contains(category_ids, item.categ_id[0])) && - (!item.date_start || moment.utc(item.date_start).isSameOrBefore(date)) && - (!item.date_end || moment.utc(item.date_end).isSameOrAfter(date)) - ); - }); + return ( + (!item.categ_id || _.contains(category_ids, item.categ_id[0])) && + (!item.date_start || moment.utc(item.date_start).isSameOrBefore(date)) && + (!item.date_end || moment.utc(item.date_end).isSameOrAfter(date)) + ); + } + ); } var price = self.lst_price; @@ -1650,7 +1652,7 @@ export class Orderline extends PosModel { var packlotline = pack_lot_lines[i][2]; var pack_lot_line = new Packlotline( {}, - { json: _.extend({...packlotline}, { order_line: this }) } + { json: _.extend({ ...packlotline }, { order_line: this }) } ); this.pack_lot_lines.add(pack_lot_line); } @@ -1790,7 +1792,7 @@ export class Orderline extends PosModel { const maxQtyToRefund = toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty; if (quant > 0) { - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: _t("Positive quantity not allowed"), body: _t( "Only a negative quantity is allowed for this refund line. Click on +/- to modify the quantity to be refunded." @@ -1802,7 +1804,7 @@ export class Orderline extends PosModel { } else if (-quant <= maxQtyToRefund) { toRefundDetail.qty = -quant; } else { - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: _t("Greater than allowed"), body: _.str.sprintf( _t( @@ -2243,7 +2245,7 @@ export class Orderline extends PosModel { }; } display_discount_policy() { - return (this.order.pricelist ? this.order.pricelist.discount_policy : "with_discount" ); + return this.order.pricelist ? this.order.pricelist.discount_policy : "with_discount"; } compute_fixed_price(price) { var order = this.order; @@ -2605,8 +2607,8 @@ export class Order extends PosModel { } this.set_partner(partner); - this.temporary = false; // FIXME - this.to_invoice = false; // FIXME + this.temporary = false; // FIXME + this.to_invoice = false; // FIXME this.shippingDate = json.shipping_date; var orderlines = json.lines; @@ -2622,10 +2624,7 @@ export class Order extends PosModel { var paymentlines = json.statement_ids; for (i = 0; i < paymentlines.length; i++) { var paymentline = paymentlines[i][2]; - var newpaymentline = new Payment( - {}, - { pos: this.pos, order: this, json: paymentline } - ); + var newpaymentline = new Payment({}, { pos: this.pos, order: this, json: paymentline }); this.paymentlines.add(newpaymentline); if (i === paymentlines.length - 1) { @@ -2686,7 +2685,10 @@ export class Order extends PosModel { _exportShippingDateForPrinting() { const shippingDate = new Date(this.shippingDate); const localeShippingDate = field_utils.format.date( - moment(shippingDate), {}, {timezone: false}); + moment(shippingDate), + {}, + { timezone: false } + ); const exportedDate = { localestring: localeShippingDate, validationDate: shippingDate, @@ -3399,7 +3401,10 @@ export class Order extends PosModel { var rounding_method = this.pos.cash_rounding[0].rounding_method; var remaining = this.get_total_with_tax() - this.get_total_paid(); var sign = this.get_total_with_tax() > 0 ? 1.0 : -1.0; - if(this.get_total_with_tax() < 0 && remaining > 0 || this.get_total_with_tax() > 0 && remaining < 0) { + if ( + (this.get_total_with_tax() < 0 && remaining > 0) || + (this.get_total_with_tax() > 0 && remaining < 0) + ) { rounding_method = rounding_method.endsWith("UP") ? "DOWN" : rounding_method; } @@ -3415,16 +3420,13 @@ export class Order extends PosModel { Math.abs(this.get_total_with_tax()) < this.pos.cash_rounding[0].rounding ) { return 0; - } else if(rounding_method === "UP" && rounding_applied < 0 && remaining > 0) { + } else if (rounding_method === "UP" && rounding_applied < 0 && remaining > 0) { rounding_applied += this.pos.cash_rounding[0].rounding; - } - else if(rounding_method === "UP" && rounding_applied > 0 && remaining < 0) { + } else if (rounding_method === "UP" && rounding_applied > 0 && remaining < 0) { rounding_applied -= this.pos.cash_rounding[0].rounding; - } - else if(rounding_method === "DOWN" && rounding_applied > 0 && remaining > 0){ + } else if (rounding_method === "DOWN" && rounding_applied > 0 && remaining > 0) { rounding_applied -= this.pos.cash_rounding[0].rounding; - } - else if(rounding_method === "DOWN" && rounding_applied < 0 && remaining < 0){ + } else if (rounding_method === "DOWN" && rounding_applied < 0 && remaining < 0) { rounding_applied += this.pos.cash_rounding[0].rounding; } return sign * rounding_applied; @@ -3587,7 +3589,7 @@ export class Order extends PosModel { } /* ---- Ship later --- */ setShippingDate(shippingDate) { - this.shippingDate = shippingDate + this.shippingDate = shippingDate; } getShippingDate() { return this.shippingDate; diff --git a/addons/point_of_sale/static/src/js/payment.js b/addons/point_of_sale/static/src/js/payment.js index 7c4bee6da3d..c8ba7149096 100644 --- a/addons/point_of_sale/static/src/js/payment.js +++ b/addons/point_of_sale/static/src/js/payment.js @@ -22,6 +22,7 @@ import core from "web.core"; * by overriding the loader_params of the models in the back end * in the `pos.session` model */ +// FIXME POSREF convert this to a class. export const PaymentInterface = core.Class.extend({ init: function (pos, payment_method) { this.pos = pos; diff --git a/addons/point_of_sale/static/src/js/printers.js b/addons/point_of_sale/static/src/js/printers.js index cc1ae86fddf..5205f37a0c2 100644 --- a/addons/point_of_sale/static/src/js/printers.js +++ b/addons/point_of_sale/static/src/js/printers.js @@ -3,7 +3,6 @@ import Session from "web.Session"; import core from "web.core"; -import { Gui } from "@point_of_sale/js/Gui"; import { ErrorPopup } from "./Popups/ErrorPopup"; var _t = core._t; @@ -115,7 +114,7 @@ export const PrinterMixin = { _onIoTActionResult: function (data) { if (this.pos && (data === false || data.result === false)) { - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: _t("Connection to the printer failed"), body: _t("Please check if the printer is still connected."), }); @@ -124,7 +123,7 @@ export const PrinterMixin = { _onIoTActionFail: function () { if (this.pos) { - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: _t("Connection to IoT Box failed"), body: _t("Please check if the IoT Box is still connected."), }); diff --git a/addons/point_of_sale/static/src/utils.js b/addons/point_of_sale/static/src/utils.js index dffb1556cfe..2ec4e530f29 100644 --- a/addons/point_of_sale/static/src/utils.js +++ b/addons/point_of_sale/static/src/utils.js @@ -41,3 +41,9 @@ export function withComputedProperties(obj, sources, descriptor) { } return obj; } + +export class Reactive { + constructor() { + return reactive(this); + } +} diff --git a/addons/point_of_sale/static/src/xml/Chrome.xml b/addons/point_of_sale/static/src/xml/Chrome.xml index ad4f76e0bfa..5ac17ee3615 100644 --- a/addons/point_of_sale/static/src/xml/Chrome.xml +++ b/addons/point_of_sale/static/src/xml/Chrome.xml @@ -11,10 +11,14 @@
+ + - + t-props="pos.mainScreen.props" t-key="pos.mainScreen.component.name"/> + + +
diff --git a/addons/point_of_sale/static/src/xml/Popups/PosPopupController.xml b/addons/point_of_sale/static/src/xml/Popups/PosPopupController.xml deleted file mode 100644 index acd752853ff..00000000000 --- a/addons/point_of_sale/static/src/xml/Popups/PosPopupController.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - -
- - - -
-
- -
diff --git a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml index e3ff4b26ba3..f6f8cbb683c 100644 --- a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml +++ b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml @@ -7,7 +7,7 @@
+ t-on-click="() => pos.showScreen('ProductScreen')"> Back
@@ -108,7 +108,7 @@ t-on-click="() => this.trigger('validate-order')">

Validate

-
diff --git a/addons/point_of_sale/static/tests/unit/test_NumberBuffer.js b/addons/point_of_sale/static/tests/unit/number_buffer_service_tests.js similarity index 72% rename from addons/point_of_sale/static/tests/unit/test_NumberBuffer.js rename to addons/point_of_sale/static/tests/unit/number_buffer_service_tests.js index 7b137f2c019..ffa8c5f32f7 100644 --- a/addons/point_of_sale/static/tests/unit/test_NumberBuffer.js +++ b/addons/point_of_sale/static/tests/unit/number_buffer_service_tests.js @@ -1,34 +1,41 @@ /** @odoo-module */ -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; -import makeTestEnvironment from "web.test_env"; import testUtils from "web.test_utils"; import { mount } from "@web/../tests/helpers/utils"; import { LegacyComponent } from "@web/legacy/legacy_component"; +import { useService } from "@web/core/utils/hooks"; +import { numberBuffer } from "@point_of_sale/app/number_buffer_service"; +import { registry } from "@web/core/registry"; -const { useState, xml } = owl; +import { makeTestEnv } from "@web/../tests/helpers/mock_env"; + +import { useState, xml } from "@odoo/owl"; QUnit.module("unit tests for NumberBuffer", { - before() {}, + async beforeEach() { + registry.category("services").add("number_buffer", numberBuffer); + registry.category("services").add("sound", { start: () => ({ play() {} }) }); + }, }); QUnit.test("simple fast inputs with capture in between", async function (assert) { assert.expect(3); const target = testUtils.prepareTarget(); - const env = makeTestEnvironment(); + const env = await makeTestEnv(); class Root extends LegacyComponent { setup() { this.state = useState({ buffer: "" }); - numberBuffer.activate(); - numberBuffer.use({ + this.numberBuffer = useService("number_buffer"); + this.numberBuffer.activate(); + this.numberBuffer.use({ nonKeyboardInputEvent: "numpad-click-input", state: this.state, }); } resetBuffer() { - numberBuffer.capture(); - numberBuffer.reset(); + this.numberBuffer.capture(); + this.numberBuffer.reset(); } onClickOne() { this.trigger("numpad-click-input", { key: "1" }); diff --git a/addons/point_of_sale/static/tests/unit/popup_controller_tests.js b/addons/point_of_sale/static/tests/unit/popup_controller_tests.js new file mode 100644 index 00000000000..522355053ac --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/popup_controller_tests.js @@ -0,0 +1,152 @@ +/** @odoo-module */ + +import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; +import { PosComponent } from "@point_of_sale/js/PosComponent"; +import makeTestEnvironment from "web.test_env"; +import { click, getFixture, mount, nextTick, triggerEvent } from "@web/../tests/helpers/utils"; +import { clearRegistryWithCleanup, makeTestEnv } from "@web/../tests/helpers/mock_env"; +import { registry } from "@web/core/registry"; +import { popupService } from "@point_of_sale/app/popup/popup_service"; +import { MainComponentsContainer } from "@web/core/main_components_container"; +import { useService } from "@web/core/utils/hooks"; + +const { xml } = owl; + +// Note that we are creating new popups here to decouple this test from the pos app. +class CustomPopup1 extends AbstractAwaitablePopup {} +CustomPopup1.template = xml/* html */ ` + +`; + +class CustomPopup2 extends AbstractAwaitablePopup {} +CustomPopup2.template = xml/* html */ ` + +`; + +class Root extends PosComponent { + static components = { MainComponentsContainer }; + setup() { + super.setup(); + this.popup = useService("popup"); + } +} +Root.template = xml``; + +let env; +QUnit.module("unit tests for PopupContainer", { + async beforeEach() { + const makeService = (obj) => ({ + start() { + return obj; + }, + }); + const legacyEnv = makeTestEnvironment(); + registry.category("services").add("popup", popupService); + registry.category("services").add("pos_notification", makeService({ add() {} })); + registry.category("services").add("sound", makeService({ play() {} })); + registry.category("services").add("pos", makeService({ legacyEnv })); + clearRegistryWithCleanup(registry.category("main_components")); + env = await makeTestEnv(); + for (const service of ["pos_notification", "sound", "popup"]) { + legacyEnv.services[service] = env.services[service]; + } + }, +}); + +QUnit.test("allow multiple popups at the same time", async function (assert) { + const fixture = getFixture(); + const root = await mount(Root, fixture, { env }); + + // Single popup + let popup1Promise = root.popup.add(CustomPopup1, {}); + await nextTick(); + assert.containsOnce(fixture, ".popup", "popup is open"); + await click(fixture.querySelector(".modal-dialog .custom-popup-1 .confirm")); + let result1 = await popup1Promise; + assert.strictEqual(result1.confirmed, true, "popup is confirmed"); + assert.containsNone(fixture, ".popup", 0, "popup is closed"); + + // Multiple popups + popup1Promise = root.popup.add(CustomPopup1, {}); + await nextTick(); + + assert.containsOnce(fixture, ".popup", 1, "first popup is shown"); + + const popup2Promise = root.popup.add(CustomPopup2, {}); + await nextTick(); + + assert.containsOnce(fixture, ".popup", "only one popup is displayed"); + assert.containsOnce(fixture, ".custom-popup-2", "second popup is displayed"); + assert.containsNone(fixture, ".custom-popup-1", "first popup is not displayed"); + + await click(fixture.querySelector(".modal-dialog .custom-popup-2 .confirm")); + + assert.containsOnce(fixture, ".popup", "after confirming on popup 2, only 1 should remain"); + assert.containsOnce(fixture, ".custom-popup-1", "first popup is displayed"); + assert.containsNone(fixture, ".custom-popup-2", "second popup is closed"); + + await click(fixture.querySelector(".modal-dialog .custom-popup-1 .cancel")); + + assert.containsNone(fixture, ".popup", "after cancelling popup 1, no popup should remain."); + + result1 = await popup1Promise; + const result2 = await popup2Promise; + assert.strictEqual(result1.confirmed, false, "popup 1 is not confirmed"); + assert.strictEqual(result2.confirmed, true, "popup 2 is confirmed"); +}); + +QUnit.test("pressing cancel/confirm key should only close the top popup", async function (assert) { + const fixture = getFixture(); + const root = await mount(Root, fixture, { env }); + + const popup1Promise = root.popup.add(CustomPopup1, { + confirmKey: "Enter", + cancelKey: "Escape", + }); + await nextTick(); + assert.containsOnce(fixture, ".custom-popup-1", "first popup is open"); + + const popup2Promise = root.popup.add(CustomPopup2, { + confirmKey: "Enter", + cancelKey: "Escape", + }); + await nextTick(); + assert.containsNone(fixture, ".custom-popup-1", "first popup no longer displayed"); + assert.containsOnce(fixture, ".custom-popup-2", "second popup is open"); + + await triggerEvent(fixture, ".popup", "keyup", { key: "Escape" }); + assert.containsNone(fixture, ".custom-popup-2", "second popup no longer displayed"); + assert.containsOnce(fixture, ".custom-popup-1", "first popup is displayed again"); + + const result2 = await popup2Promise; + assert.strictEqual( + result2.confirmed, + false, + "canceling the popup with escape resolved the promise with confirmed: false" + ); + + await triggerEvent(fixture, ".popup", "keyup", { key: "Enter" }); + assert.containsNone(fixture, ".popup", "no popups remain"); + + const result1 = await popup1Promise; + assert.strictEqual( + result1.confirmed, + true, + "confirming the popup with enter resolved the promise with confirmed: true" + ); +}); diff --git a/addons/point_of_sale/static/tests/unit/test_PosPopupController.js b/addons/point_of_sale/static/tests/unit/test_PosPopupController.js deleted file mode 100644 index 5cc8d2c96fe..00000000000 --- a/addons/point_of_sale/static/tests/unit/test_PosPopupController.js +++ /dev/null @@ -1,181 +0,0 @@ -/** @odoo-module */ - -import { PosPopupController } from "@point_of_sale/js/Popups/PosPopupController"; -import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; -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; - -// Note that we are creating new popups here to decouple this test from the pos app. -class CustomPopup1 extends AbstractAwaitablePopup {} -CustomPopup1.template = xml/* html */ ` - -`; - -class CustomPopup2 extends AbstractAwaitablePopup {} -CustomPopup2.template = xml/* html */ ` - -`; - -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, - }); - } - } - Root.template = xml/* html */ ` -
- -
- `; - - const root = await mount(Root, testUtils.prepareTarget(), { env }); - - // Check 1 popup - let popup1Promise = root.showPopup(CustomPopup1, {}); - await testUtils.nextTick(); - assert.strictEqual(root.el.querySelectorAll(".popup").length, 1); - testUtils.dom.click(root.el.querySelector(".modal-dialog .custom-popup-1 .confirm")); - let result1 = await popup1Promise; - assert.strictEqual(result1.confirmed, true); - await testUtils.nextTick(); - assert.strictEqual(root.el.querySelectorAll(".popup").length, 0); - - // Check multiple popups - popup1Promise = root.showPopup(CustomPopup1, {}); - await testUtils.nextTick(); - - // Check if the first popup is shown. - assert.strictEqual(root.el.querySelectorAll(".popup").length, 1); - - const popup2Promise = root.showPopup(CustomPopup2, {}); - await testUtils.nextTick(); - - // Check for the second popup. - assert.strictEqual(root.el.querySelectorAll(".popup").length, 2); - - // popup 1 should be hidden - assert.strictEqual(root.el.querySelectorAll(".modal-dialog.oe_hidden").length, 1); - - // click confirm on popup 2 - testUtils.dom.click(root.el.querySelector(".modal-dialog .custom-popup-2 .confirm")); - await testUtils.nextTick(); - - // after confirming on popup 2, only 1 should remain. - assert.strictEqual(root.el.querySelectorAll(".popup").length, 1); - assert.strictEqual(root.el.querySelectorAll(".modal-dialog .custom-popup-2").length, 0); - - // popup 1 should not be hidden - const customPopup1 = root.el.querySelector(".modal-dialog"); - assert.strictEqual(![...customPopup1.classList].includes("oe_hidden"), true); - testUtils.dom.click(root.el.querySelector(".modal-dialog .custom-popup-1 .cancel")); - await testUtils.nextTick(); - - // after cancelling popup 1, no popup should remain. - assert.strictEqual(root.el.querySelectorAll(".popup").length, 0); - - result1 = await popup1Promise; - const result2 = await popup2Promise; - assert.strictEqual(result1.confirmed, false); // false because it's cancelled. - assert.strictEqual(result2.confirmed, true); // true because it's confirmed. -}); - -QUnit.test("pressing cancel/confirm key should only close the top popup", async function (assert) { - assert.expect(6); - - class Root extends PosComponent { - static components = { PosPopupController }; - setup() { - super.setup(); - useSubEnv({ - isDebug: () => false, - }); - } - } - Root.template = xml/* html */ ` -
- -
- `; - - const root = await mount(Root, testUtils.prepareTarget(), { env }); - - const popup1Promise = root.showPopup(CustomPopup1, { - confirmKey: "Enter", - cancelKey: "Escape", - }); - await testUtils.nextTick(); - assert.strictEqual(root.el.querySelectorAll(".popup").length, 1); - - const popup2Promise = root.showPopup(CustomPopup2, { - confirmKey: "Enter", - cancelKey: "Escape", - }); - await testUtils.nextTick(); - assert.strictEqual(root.el.querySelectorAll(".popup").length, 2); - - // Pressing 'Escape' should cancel the top popup which is the CustomPopup2. - testUtils.dom.triggerEvent(window, "keyup", { key: "Escape" }); - await testUtils.nextTick(); - - // Therefore, the popup2Promise has now resolved with `confirmed` value = false. - const result2 = await popup2Promise; - assert.strictEqual(result2.confirmed, false); - - assert.strictEqual(root.el.querySelectorAll(".popup").length, 1); - - testUtils.dom.triggerEvent(window, "keyup", { key: "Enter" }); - await testUtils.nextTick(); - - assert.strictEqual(root.el.querySelectorAll(".popup").length, 0); - - const result1 = await popup1Promise; - assert.strictEqual(result1.confirmed, true); -}); diff --git a/addons/pos_adyen/static/src/js/payment_adyen.js b/addons/pos_adyen/static/src/js/payment_adyen.js index 60f01d40691..bcd9b1b8f39 100644 --- a/addons/pos_adyen/static/src/js/payment_adyen.js +++ b/addons/pos_adyen/static/src/js/payment_adyen.js @@ -3,7 +3,6 @@ import core from "web.core"; import rpc from "web.rpc"; import { PaymentInterface } from "@point_of_sale/js/payment"; -import { Gui } from "@point_of_sale/js/Gui"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; var _t = core._t; @@ -366,7 +365,7 @@ export const PaymentAdyen = PaymentInterface.extend({ if (!title) { title = _t("Adyen Error"); } - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: title, body: msg, }); diff --git a/addons/pos_discount/static/src/js/DiscountButton.js b/addons/pos_discount/static/src/js/DiscountButton.js index ae8859bc6dd..db101c2ee1b 100644 --- a/addons/pos_discount/static/src/js/DiscountButton.js +++ b/addons/pos_discount/static/src/js/DiscountButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; @@ -11,11 +11,12 @@ export class DiscountButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("click", this.onClick); } async onClick() { var self = this; - const { confirmed, payload } = await this.showPopup(NumberPopup, { + const { confirmed, payload } = await this.popup.add(NumberPopup, { title: this.env._t("Discount Percentage"), startingValue: this.env.pos.config.discount_pc, isInputSelected: true, @@ -31,7 +32,7 @@ export class DiscountButton extends PosComponent { var lines = order.get_orderlines(); var product = this.env.pos.db.get_product_by_id(this.env.pos.config.discount_product_id[0]); if (product === undefined) { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: this.env._t("No discount product found"), body: this.env._t( "The discount product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'." diff --git a/addons/pos_hr/static/src/js/CashierName.js b/addons/pos_hr/static/src/js/CashierName.js index f396b0329c4..43cef7bc3b2 100644 --- a/addons/pos_hr/static/src/js/CashierName.js +++ b/addons/pos_hr/static/src/js/CashierName.js @@ -5,8 +5,8 @@ import { patch } from "@web/core/utils/patch"; import { SelectCashierMixin } from "@pos_hr/js/SelectCashierMixin"; import { useBarcodeReader } from "@point_of_sale/js/custom_hooks"; -patch(CashierName.prototype, "pos_hr.CashierName", { - ...SelectCashierMixin, +patch(CashierName.prototype, "pos_hr.CashierName SelectCashierMixin", SelectCashierMixin); +patch(CashierName.prototype, "pos_hr.CashierName methods", { setup() { this._super(...arguments); useBarcodeReader({ cashier: this.barcodeCashierAction }); diff --git a/addons/pos_hr/static/src/js/Chrome.js b/addons/pos_hr/static/src/js/Chrome.js index 33615a0ee27..cdcf8dbc8d1 100644 --- a/addons/pos_hr/static/src/js/Chrome.js +++ b/addons/pos_hr/static/src/js/Chrome.js @@ -7,7 +7,7 @@ patch(Chrome.prototype, "pos_hr.Chrome", { async start() { await this._super(...arguments); if (this.env.pos.config.module_pos_hr) { - this.showTempScreen("LoginScreen"); + this.pos.showTempScreen("LoginScreen"); } }, get showCashMoveButton() { diff --git a/addons/pos_hr/static/src/js/HeaderLockButton.js b/addons/pos_hr/static/src/js/HeaderLockButton.js index 3c50d19a5de..ad5fa00b935 100644 --- a/addons/pos_hr/static/src/js/HeaderLockButton.js +++ b/addons/pos_hr/static/src/js/HeaderLockButton.js @@ -1,5 +1,6 @@ /** @odoo-module */ +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; const { useState } = owl; @@ -10,10 +11,11 @@ export class HeaderLockButton extends PosComponent { setup() { super.setup(); this.state = useState({ isUnlockIcon: true, title: "Unlocked" }); + this.pos = usePos(); } async showLoginScreen() { this.env.pos.reset_cashier(); - await this.showTempScreen("LoginScreen"); + await this.pos.showTempScreen("LoginScreen"); } onMouseOver(isMouseOver) { this.state.isUnlockIcon = !isMouseOver; diff --git a/addons/pos_hr/static/src/js/LoginScreen.js b/addons/pos_hr/static/src/js/LoginScreen.js index bc5503d594e..1f5f728260a 100644 --- a/addons/pos_hr/static/src/js/LoginScreen.js +++ b/addons/pos_hr/static/src/js/LoginScreen.js @@ -5,16 +5,18 @@ import { SelectCashierMixin } from "@pos_hr/js/SelectCashierMixin"; import { useBarcodeReader } from "@point_of_sale/js/custom_hooks"; import { registry } from "@web/core/registry"; import { patch } from "@web/core/utils/patch"; +import { usePos } from "@point_of_sale/app/pos_hook"; export class LoginScreen extends PosComponent { static template = "LoginScreen"; } -// FIXME stop this double patch once the mixin is converted to a hook. +// FIXME POSREF stop this double patch once the mixin is converted to a hook. patch(LoginScreen.prototype, "pos_hr.LoginScreen SelectCashierMixin", SelectCashierMixin); patch(LoginScreen.prototype, "pos_hr.LoginScreen methods", { setup() { this._super(...arguments); useBarcodeReader({ cashier: this.barcodeCashierAction }, true); + this.pos = usePos(); }, async selectCashier() { if (await this._super(...arguments)) { @@ -22,19 +24,19 @@ patch(LoginScreen.prototype, "pos_hr.LoginScreen methods", { } }, async barcodeCashierAction(code) { - if (await this._super(code) && this.env.pos.get_cashier().id) { + if ((await this._super(code)) && this.env.pos.get_cashier().id) { this.back(); } }, back() { this.props.resolve({ confirmed: false, payload: false }); - this.trigger("close-temp-screen"); + this.pos.closeTempScreen(); this.env.pos.hasLoggedIn = true; this.env.posbus.trigger("start-cash-control"); }, confirm() { this.props.resolve({ confirmed: true, payload: true }); - this.trigger("close-temp-screen"); + this.pos.closeTempScreen(); }, get shopName() { return this.env.pos.config.name; diff --git a/addons/pos_hr/static/src/js/SelectCashierMixin.js b/addons/pos_hr/static/src/js/SelectCashierMixin.js index 087b0012617..cee18bbc756 100644 --- a/addons/pos_hr/static/src/js/SelectCashierMixin.js +++ b/addons/pos_hr/static/src/js/SelectCashierMixin.js @@ -4,11 +4,16 @@ import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { useService } from "@web/core/utils/hooks"; // FIXME POSREF make this into a hook or something export const SelectCashierMixin = { + setup() { + this._super(...arguments); + this.popup = useService("popup"); + }, async askPin(employee) { - const { confirmed, payload: inputPin } = await this.showPopup(NumberPopup, { + const { confirmed, payload: inputPin } = await this.popup.add(NumberPopup, { isPassword: true, title: this.env._t("Password ?"), startingValue: null, @@ -21,7 +26,7 @@ export const SelectCashierMixin = { if (employee.pin === Sha1.hash(inputPin)) { return employee; } else { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: this.env._t("Incorrect Password"), }); return; @@ -43,7 +48,7 @@ export const SelectCashierMixin = { isSelected: false, }; }); - let { confirmed, payload: employee } = await this.showPopup(SelectionPopup, { + let { confirmed, payload: employee } = await this.popup.add(SelectionPopup, { title: this.env._t("Change Cashier"), list: employeesList, }); diff --git a/addons/pos_hr_restaurant/__manifest__.py b/addons/pos_hr_restaurant/__manifest__.py index a2c56c7002c..ad8b794e405 100644 --- a/addons/pos_hr_restaurant/__manifest__.py +++ b/addons/pos_hr_restaurant/__manifest__.py @@ -14,7 +14,7 @@ This module adapts the behavior of the PoS when the pos_hr and pos_restaurant ar 'auto_install': True, 'assets': { 'point_of_sale.assets': [ - 'pos_hr_restaurant/static/src/js/**/*.js', + 'pos_hr_restaurant/static/src/**/*', ], }, 'license': 'LGPL-3', diff --git a/addons/pos_hr_restaurant/static/src/app/pos_store.js b/addons/pos_hr_restaurant/static/src/app/pos_store.js new file mode 100644 index 00000000000..bc5e603dbf5 --- /dev/null +++ b/addons/pos_hr_restaurant/static/src/app/pos_store.js @@ -0,0 +1,11 @@ +/* @odoo-module alias=pos_restaurant_hr.chrome */ + +import { patch } from "@web/core/utils/patch"; +import "@pos_restaurant/app/pos_store"; +import { PosStore } from "@point_of_sale/app/pos_store"; + +patch(PosStore.prototype, "pos_hr_restaurant.PosStore", { + shouldResetIdleTimer() { + return this.tempScreen?.name !== "LoginScreen" && this._super(...arguments); + }, +}); diff --git a/addons/pos_hr_restaurant/static/src/js/Chrome.js b/addons/pos_hr_restaurant/static/src/js/Chrome.js deleted file mode 100644 index 673c17d2aa4..00000000000 --- a/addons/pos_hr_restaurant/static/src/js/Chrome.js +++ /dev/null @@ -1,11 +0,0 @@ -/* @odoo-module alias=pos_restaurant_hr.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.pos.tempScreen?.name !== "LoginScreen"; - }, -}); diff --git a/addons/pos_loyalty/static/src/js/ControlButtons/PromoCodeButton.js b/addons/pos_loyalty/static/src/js/ControlButtons/PromoCodeButton.js index 5868320b13b..bd27c094f9a 100644 --- a/addons/pos_loyalty/static/src/js/ControlButtons/PromoCodeButton.js +++ b/addons/pos_loyalty/static/src/js/ControlButtons/PromoCodeButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { TextInputPopup } from "@point_of_sale/js/Popups/TextInputPopup"; export class PromoCodeButton extends PosComponent { @@ -10,11 +10,12 @@ export class PromoCodeButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("click", this.onClick); } async onClick() { - let { confirmed, payload: code } = await this.showPopup(TextInputPopup, { + let { confirmed, payload: code } = await this.popup.add(TextInputPopup, { title: this.env._t("Enter Code"), startingValue: "", placeholder: this.env._t("Gift card or Discount code"), diff --git a/addons/pos_loyalty/static/src/js/ControlButtons/RewardButton.js b/addons/pos_loyalty/static/src/js/ControlButtons/RewardButton.js index 5167644d62b..6b6fda5e6bc 100644 --- a/addons/pos_loyalty/static/src/js/ControlButtons/RewardButton.js +++ b/addons/pos_loyalty/static/src/js/ControlButtons/RewardButton.js @@ -1,9 +1,8 @@ /** @odoo-module **/ -import { Gui } from "@point_of_sale/js/Gui"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; export class RewardButton extends PosComponent { @@ -11,6 +10,8 @@ export class RewardButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); + this.notification = useService("pos_notification"); useListener("click", this.onClick); } @@ -70,7 +71,7 @@ export class RewardButton extends PosComponent { label: this.env.pos.db.get_product_by_id(product_id).display_name, item: product_id, })); - const { confirmed, payload: selectedProduct } = await this.showPopup(SelectionPopup, { + const { confirmed, payload: selectedProduct } = await this.popup.add(SelectionPopup, { title: this.env._t("Please select a product for this reward"), list: productsList, }); @@ -93,7 +94,7 @@ export class RewardButton extends PosComponent { const result = order._applyReward(reward, coupon_id, args); if (result !== true) { // Returned an error - Gui.showNotification(result); + this.notification.add(result); } order._updateRewards(); return result; @@ -114,7 +115,7 @@ export class RewardButton extends PosComponent { label: reward.reward.description, item: reward, })); - const { confirmed, payload: selectedReward } = await this.showPopup(SelectionPopup, { + const { confirmed, payload: selectedReward } = await this.popup.add(SelectionPopup, { title: this.env._t("Please select a reward"), list: rewardsList, }); diff --git a/addons/pos_loyalty/static/src/js/ControlButtons/eWalletButton.js b/addons/pos_loyalty/static/src/js/ControlButtons/eWalletButton.js index cbab3248efa..30886f4f8fc 100644 --- a/addons/pos_loyalty/static/src/js/ControlButtons/eWalletButton.js +++ b/addons/pos_loyalty/static/src/js/ControlButtons/eWalletButton.js @@ -4,10 +4,16 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { useService } from "@web/core/utils/hooks"; export class eWalletButton extends PosComponent { static template = "point_of_sale.eWalletButton"; + setup() { + super.setup(...arguments); + this.popup = useService("popup"); + } + _getEWalletRewards(order) { const claimableRewards = order.getClaimableRewards(); const eWalletRewards = claimableRewards.filter( @@ -28,7 +34,7 @@ export class eWalletButton extends PosComponent { if (eWalletPrograms.length == 1) { selectedProgram = eWalletPrograms[0]; } else { - const { confirmed, payload } = await this.showPopup(SelectionPopup, { + const { confirmed, payload } = await this.popup.add(SelectionPopup, { title: this.env._t("Refund with eWallet"), list: eWalletPrograms.map((program) => ({ id: program.id, @@ -55,7 +61,7 @@ export class eWalletButton extends PosComponent { if (eWalletRewards.length == 1) { eWalletReward = eWalletRewards[0]; } else { - const { confirmed, payload } = await this.showPopup(SelectionPopup, { + const { confirmed, payload } = await this.popup.add(SelectionPopup, { title: this.env._t("Use eWallet to pay"), list: eWalletRewards.map(({ reward, coupon_id }) => ({ id: reward.id, @@ -75,7 +81,7 @@ export class eWalletButton extends PosComponent { ); if (result !== true) { // Returned an error - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Error"), body: result, }); diff --git a/addons/pos_loyalty/static/src/js/Loyalty.js b/addons/pos_loyalty/static/src/js/Loyalty.js index 0319ecc4461..05d5f125d8b 100644 --- a/addons/pos_loyalty/static/src/js/Loyalty.js +++ b/addons/pos_loyalty/static/src/js/Loyalty.js @@ -3,7 +3,6 @@ import { Order, Orderline, PosGlobalState } from "@point_of_sale/js/models"; import session from "web.session"; import concurrency from "web.concurrency"; -import { Gui } from "@point_of_sale/js/Gui"; import { round_decimals, round_precision } from "web.utils"; import core from "web.core"; import { patch } from "@web/core/utils/patch"; @@ -1627,7 +1626,7 @@ patch(Order.prototype, "pos_loyalty.Order", { // Allow rejecting a gift card that is not yet paid. const program = this.pos.program_by_id[payload.program_id]; if (program && program.program_type === "gift_card" && !payload.has_source_order) { - const { confirmed } = await Gui.showPopup(ConfirmPopup, { + const { confirmed } = await this.pos.env.services.popup.add(ConfirmPopup, { title: _t("Unpaid gift card"), body: _t( "This gift card is not linked to any order. Do you really want to apply its reward?" @@ -1666,7 +1665,7 @@ patch(Order.prototype, "pos_loyalty.Order", { async activateCode(code) { const res = await this._activateCode(code); if (res !== true) { - Gui.showNotification(res); + this.pos.env.services.pos_notification.add(res); } }, isProgramsResettable() { diff --git a/addons/pos_loyalty/static/src/js/PaymentScreen.js b/addons/pos_loyalty/static/src/js/PaymentScreen.js index c0f2368ec90..30363ab9769 100644 --- a/addons/pos_loyalty/static/src/js/PaymentScreen.js +++ b/addons/pos_loyalty/static/src/js/PaymentScreen.js @@ -62,7 +62,7 @@ patch(PaymentScreen.prototype, "pos_loyalty.PaymentScreen", { ); } if (!successful) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Error validating rewards"), body: payload.message, }); diff --git a/addons/pos_loyalty/static/src/js/ProductScreen.js b/addons/pos_loyalty/static/src/js/ProductScreen.js index 477159c2467..31028b86b8e 100644 --- a/addons/pos_loyalty/static/src/js/ProductScreen.js +++ b/addons/pos_loyalty/static/src/js/ProductScreen.js @@ -7,10 +7,12 @@ import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; import { TextInputPopup } from "@point_of_sale/js/Popups/TextInputPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; +import { useService } from "@web/core/utils/hooks"; patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { setup() { this._super(...arguments); + this.notification = useService("pos_notification"); useBarcodeReader({ coupon: this._onCouponScan, }); @@ -21,12 +23,12 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { .get_orderlines() .find((line) => line.getEWalletGiftCardProgramType() === "ewallet"); if (eWalletLine && !order.get_partner()) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Customer needed"), body: this.env._t("eWallet requires a customer to be selected"), }); if (confirmed) { - const { confirmed, payload: newPartner } = await this.showTempScreen( + const { confirmed, payload: newPartner } = await this.pos.showTempScreen( "PartnerListScreen", { partner: null } ); @@ -52,7 +54,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { // If gift card program setting is 'scan_use', ask for the code. if (this.env.pos.config.gift_card_settings == "scan_use") { - const { confirmed, payload: code } = await this.showPopup(TextInputPopup, { + const { confirmed, payload: code } = await this.popup.add(TextInputPopup, { title: this.env._t("Generate a Gift Card"), startingValue: "", placeholder: this.env._t("Enter the gift card code"), @@ -78,7 +80,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { // There should be maximum one gift card for a given code. const giftCard = fetchedGiftCard[0]; if (giftCard && giftCard.source_pos_order_id) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("This gift card has already been sold"), body: this.env._t( "You cannot sell a gift card that has already been sold." @@ -95,7 +97,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { options.giftCardId = giftCard.id; } } else { - this.showNotification("Please enter a valid gift card code."); + this.notification.add("Please enter a valid gift card code."); return false; } } @@ -117,7 +119,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { const linkedPrograms = linkedProgramIds.map((id) => this.env.pos.program_by_id[id]); let selectedProgram = null; if (linkedPrograms.length > 1) { - const { confirmed, payload: program } = await this.showPopup(SelectionPopup, { + const { confirmed, payload: program } = await this.popup.add(SelectionPopup, { title: this.env._t("Select program"), list: linkedPrograms.map((program) => ({ id: program.id, @@ -173,7 +175,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { if (selectedLine.eWalletGiftCardProgram) { // Do not allow negative quantity or price in a gift card or ewallet orderline. // Refunding gift card or ewallet is not supported. - this.showNotification( + this.notification.add( this.env._t( "You cannot set negative quantity or price to gift card or ewallet." ), @@ -189,7 +191,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { (event.detail.key === "Backspace" || event.detail.key === "Delete") ) { const reward = this.env.pos.reward_by_id[selectedLine.reward_id]; - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Deactivating reward"), body: _.str.sprintf( this.env._t( diff --git a/addons/pos_loyalty/static/src/js/TicketScreen.js b/addons/pos_loyalty/static/src/js/TicketScreen.js index 053b8688498..dcd094d81db 100644 --- a/addons/pos_loyalty/static/src/js/TicketScreen.js +++ b/addons/pos_loyalty/static/src/js/TicketScreen.js @@ -1,23 +1,27 @@ /** @odoo-module **/ import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScreen"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; +import { useService } from "@web/core/utils/hooks"; import { patch } from "@web/core/utils/patch"; /** * Prevent refunding ewallet/gift card lines. */ patch(TicketScreen.prototype, "pos_loyalty.TicketScreen", { + setup() { + this._super(...arguments); + this.notification = useService("pos_notification"); + }, _onUpdateSelectedOrderline() { const order = this.getSelectedSyncedOrder(); if (!order) { - return numberBuffer.reset(); + return this.numberBuffer.reset(); } const selectedOrderlineId = this.getSelectedOrderlineId(); const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId); if (orderline && this._isEWalletGiftCard(orderline)) { this._showNotAllowedRefundNotification(); - return numberBuffer.reset(); + return this.numberBuffer.reset(); } return this._super(...arguments); }, @@ -31,7 +35,7 @@ patch(TicketScreen.prototype, "pos_loyalty.TicketScreen", { return this._super(...arguments); }, _showNotAllowedRefundNotification() { - this.showNotification( + this.notification.add( this.env._t( "Refunding a top up or reward product for an eWallet or gift card program is not allowed." ), diff --git a/addons/pos_mercury/static/src/js/PaymentScreen.js b/addons/pos_mercury/static/src/js/PaymentScreen.js index 9f4d4ae196e..78704a1879a 100644 --- a/addons/pos_mercury/static/src/js/PaymentScreen.js +++ b/addons/pos_mercury/static/src/js/PaymentScreen.js @@ -3,7 +3,6 @@ import { _t } from "web.core"; import { patch } from "@web/core/utils/patch"; import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; import { useBarcodeReader } from "@point_of_sale/js/custom_hooks"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { PaymentTransactionPopup } from "./PaymentTransactionPopup"; @@ -128,7 +127,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", { * @override */ async validateOrder(isForceValidate) { - numberBuffer.capture(); + this.numberBuffer.capture(); return this._super(...arguments); }, /** @@ -137,7 +136,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", { * @override */ async _sendPaymentRequest({ detail: line }) { - numberBuffer.capture(); + this.numberBuffer.capture(); return this._super(...arguments); }, _get_swipe_pending_line() { @@ -212,7 +211,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", { credit_code_transaction(parsed_result, old_deferred, retry_nr) { var order = this.env.pos.get_order(); if (order.get_due(order.selected_paymentline) < 0) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Refunds not supported"), body: this.env._t( "Credit card refunds are not supported. Instead select your credit card payment method, click 'Validate' and refund the original charge manually through the Vantiv backend." @@ -229,7 +228,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", { var decodedMagtek = self.env.pos.decodeMagtek(parsed_result.code); if (!decodedMagtek) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Could not read card"), body: this.env._t( "This can be caused by a badly executed swipe or by not having your keyboard layout set to US QWERTY (not US International)." @@ -354,7 +353,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", { order.selected_paymentline.mercury_data = response; // used to reverse transactions order.selected_paymentline.set_credit_card_name(); - numberBuffer.reset(); + this.numberBuffer.reset(); order.trigger("change", order); // needed so that export_to_JSON gets triggered self.render(); @@ -421,22 +420,24 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", { isSelected: false, item: paymentMethod.item, })); - this.showPopup(SelectionPopup, { - title: this.env._t("Pay with: "), - list: selectionList, - }).then(({ confirmed, payload: selectedPaymentMethod }) => { - if (confirmed) { - parsed_result.payment_method_id = selectedPaymentMethod; - this.credit_code_transaction(parsed_result); - } else { - this.credit_code_cancel(); - } - }); + this.popup + .add(SelectionPopup, { + title: this.env._t("Pay with: "), + list: selectionList, + }) + .then(({ confirmed, payload: selectedPaymentMethod }) => { + if (confirmed) { + parsed_result.payment_method_id = selectedPaymentMethod; + this.credit_code_transaction(parsed_result); + } else { + this.credit_code_cancel(); + } + }); } }, remove_paymentline_by_ref(line) { this.env.pos.get_order().remove_paymentline(line); - numberBuffer.reset(); + this.numberBuffer.reset(); this.render(); }, do_reversal(line, is_voidsale, old_deferred, retry_nr) { @@ -446,7 +447,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", { // show the transaction popup. // the transaction deferred is used to update transaction status - this.showPopup(PaymentTransactionPopup, { + this.popup.add(PaymentTransactionPopup, { transaction: def, }); diff --git a/addons/pos_mercury/static/src/js/PosPopupController.js b/addons/pos_mercury/static/src/js/PosPopupController.js deleted file mode 100644 index e408bf835a9..00000000000 --- a/addons/pos_mercury/static/src/js/PosPopupController.js +++ /dev/null @@ -1,9 +0,0 @@ -/* @odoo-module */ - -import { PosPopupController } from "@point_of_sale/js/Popups/PosPopupController"; -import { patch } from "@web/core/utils/patch"; -import { PaymentTransactionPopup } from "./PaymentTransactionPopup"; - -patch(PosPopupController, "pos_mercury.PosPopupController", { - components: { ...PosPopupController.components, PaymentTransactionPopup }, -}); diff --git a/addons/pos_mercury/static/src/js/ProductScreen.js b/addons/pos_mercury/static/src/js/ProductScreen.js index 46088f624f1..83eaa238173 100644 --- a/addons/pos_mercury/static/src/js/ProductScreen.js +++ b/addons/pos_mercury/static/src/js/ProductScreen.js @@ -13,7 +13,7 @@ patch(ProductScreen.prototype, "pos_mercury.ProductScreen", { }); }, credit_error_action() { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { body: this.env._t("Go to payment screen to use cards"), }); }, diff --git a/addons/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js b/addons/pos_restaurant/static/src/app/navbar/BackToFloorButton.js similarity index 80% rename from addons/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js rename to addons/pos_restaurant/static/src/app/navbar/BackToFloorButton.js index e9dae09141f..2224ccee1a8 100644 --- a/addons/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js +++ b/addons/pos_restaurant/static/src/app/navbar/BackToFloorButton.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import { usePos } from "@point_of_sale/app/pos_store"; +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; export class BackToFloorButton extends PosComponent { @@ -20,6 +20,6 @@ export class BackToFloorButton extends PosComponent { return this.table != null; } backToFloorScreen() { - this.showScreen("FloorScreen", { floor: this.floor }); + this.pos.showScreen("FloorScreen", { floor: this.floor }); } } diff --git a/addons/pos_restaurant/static/src/app/navbar.js b/addons/pos_restaurant/static/src/app/navbar/navbar.js similarity index 75% rename from addons/pos_restaurant/static/src/app/navbar.js rename to addons/pos_restaurant/static/src/app/navbar/navbar.js index b1cc74460a3..cbd59937e48 100644 --- a/addons/pos_restaurant/static/src/app/navbar.js +++ b/addons/pos_restaurant/static/src/app/navbar/navbar.js @@ -1,8 +1,8 @@ /** @odoo-module */ import { Navbar } from "@point_of_sale/app/navbar/navbar"; -import { BackToFloorButton } from "../js/ChromeWidgets/BackToFloorButton"; import { patch } from "@web/core/utils/patch"; +import { BackToFloorButton } from "./BackToFloorButton"; patch(Navbar, "pos_restaurant.Navbar", { components: { ...Navbar.components, BackToFloorButton }, diff --git a/addons/pos_restaurant/static/src/app/navbar.xml b/addons/pos_restaurant/static/src/app/navbar/navbar.xml similarity index 100% rename from addons/pos_restaurant/static/src/app/navbar.xml rename to addons/pos_restaurant/static/src/app/navbar/navbar.xml diff --git a/addons/pos_restaurant/static/src/app/popup/popup_service.js b/addons/pos_restaurant/static/src/app/popup/popup_service.js new file mode 100644 index 00000000000..ce52141ce7b --- /dev/null +++ b/addons/pos_restaurant/static/src/app/popup/popup_service.js @@ -0,0 +1,24 @@ +/* @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { popupService } from "@point_of_sale/app/popup/popup_service"; + +patch(popupService, "pos_restaurant.popupService", { + start() { + return Object.assign(this._super(...arguments), { + closePopupsButError() { + const popups = Object.values(this.popups); + const isErrorPopupOpen = popups.some((popup) => + // FIXME POSREF: this seems very brittle. + popup.component.name.toLowerCase().includes("error") + ); + if (!isErrorPopupOpen) { + for (const popup of popups) { + popup.props.close(false); + } + } + return !isErrorPopupOpen; + }, + }); + }, +}); diff --git a/addons/pos_restaurant/static/src/app/pos_store.js b/addons/pos_restaurant/static/src/app/pos_store.js new file mode 100644 index 00000000000..3f8b1600126 --- /dev/null +++ b/addons/pos_restaurant/static/src/app/pos_store.js @@ -0,0 +1,89 @@ +/** @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { PosStore } from "@point_of_sale/app/pos_store"; +import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen"; +import { FloorScreen } from "@pos_restaurant/js/Screens/FloorScreen/FloorScreen"; + +const NON_IDLE_EVENTS = [ + "mousemove", + "mousedown", + "touchstart", + "touchend", + "touchmove", + "click", + "scroll", + "keypress", +]; +let IDLE_TIMER_SETTER; + +/** + * Go back to the floor screen if the user has been idle for a while. + */ +patch(PosStore.prototype, "pos_restaurant.PosStore", { + /** + * @override + */ + setup() { + this._super(...arguments); + this.globalState.ready.then(() => { + if (this.globalState.config.iface_floorplan) { + this.setActivityListeners(); + } + }); + }, + setActivityListeners() { + IDLE_TIMER_SETTER = this.setIdleTimer.bind(this); + for (const event of NON_IDLE_EVENTS) { + window.addEventListener(event, IDLE_TIMER_SETTER); + } + }, + setIdleTimer() { + clearTimeout(this.idleTimer); + if (this.shouldResetIdleTimer()) { + this.idleTimer = setTimeout(() => this.actionAfterIdle(), 60000); + } + }, + async actionAfterIdle() { + const isPopupClosed = this.popup.closePopupsButError(); + if (isPopupClosed) { + this.closeTempScreen(); + const { table } = this.globalState; + const order = this.globalState.get_order(); + if (order && order.get_screen_data().name === "ReceiptScreen") { + // When the order is finalized, we can safely remove it from the memory + // We check that it's in ReceiptScreen because we want to keep the order if it's in a tipping state + this.globalState.removeOrder(order); + } + this.showScreen("FloorScreen", { floor: table?.floor }); + } + }, + shouldResetIdleTimer() { + const stayPaymentScreen = + this.mainScreen.component === PaymentScreen && + this.globalState.get_order().paymentlines.length > 0; + return ( + this.globalState.config.iface_floorplan && + !stayPaymentScreen && + this.mainScreen.component !== FloorScreen + ); + }, + showScreen() { + this._super(...arguments); + this.setIdleTimer(); + }, + /** + * @override + * FIXME POSREF this is now dead code, check if we still want this + * Before closing pos, we remove the event listeners set on window + * for detecting activities outside FloorScreen. + */ + async _closePos() { + if (IDLE_TIMER_SETTER) { + for (const event of NON_IDLE_EVENTS) { + window.removeEventListener(event, IDLE_TIMER_SETTER); + } + } + return this._super(...arguments); + }, +}); diff --git a/addons/pos_restaurant/static/src/js/Chrome.js b/addons/pos_restaurant/static/src/js/Chrome.js index 02d4a324cc9..c439668e1b4 100644 --- a/addons/pos_restaurant/static/src/js/Chrome.js +++ b/addons/pos_restaurant/static/src/js/Chrome.js @@ -1,34 +1,12 @@ /** @odoo-module */ -import { Chrome } from "@point_of_sale/js/Chrome"; import { patch } from "@web/core/utils/patch"; - -const NON_IDLE_EVENTS = - "mousemove mousedown touchstart touchend touchmove click scroll keypress".split(/\s+/); -let IDLE_TIMER_SETTER; +import { Chrome } from "@point_of_sale/js/Chrome"; patch(Chrome.prototype, "pos_restaurant.Chrome", { /** * @override - */ - async start() { - await this._super(...arguments); - if (this.env.pos.config.iface_floorplan) { - this._setActivityListeners(); - } - }, - /** - * @override - * Do not set `FloorScreen` to the order. - */ - _setScreenData(name) { - if (name === "FloorScreen") { - return; - } - this._super(...arguments); - }, - /** - * @override + * FIXME POSREF move to pos store * `FloorScreen` is the start screen if there are floors. */ get startScreen() { @@ -39,63 +17,4 @@ patch(Chrome.prototype, "pos_restaurant.Chrome", { return this._super(...arguments); } }, - _setActivityListeners() { - IDLE_TIMER_SETTER = this._setIdleTimer.bind(this); - for (const event of NON_IDLE_EVENTS) { - window.addEventListener(event, IDLE_TIMER_SETTER); - } - }, - _setIdleTimer() { - clearTimeout(this.idleTimer); - if (this._shouldResetIdleTimer()) { - this.idleTimer = setTimeout(() => { - this._actionAfterIdle(); - }, 60000); - } - }, - async _actionAfterIdle() { - const isPopupClosed = await new Promise((resolve) => - this.env.posbus.trigger("close-popups-but-error", { resolve }) - ); - if (isPopupClosed) { - if (this.pos.tempScreen) { - this.trigger("close-temp-screen"); - } - const table = this.env.pos.table; - const order = this.env.pos.get_order(); - if (order && order.get_screen_data().name === "ReceiptScreen") { - // When the order is finalized, we can safely remove it from the memory - // We check that it's in ReceiptScreen because we want to keep the order if it's in a tipping state - this.env.pos.removeOrder(order); - } - this.showScreen("FloorScreen", { floor: table ? table.floor : null }); - } - }, - _shouldResetIdleTimer() { - const stayPaymentScreen = - this.pos.mainScreen.name === "PaymentScreen" && - this.env.pos.get_order().paymentlines.length > 0; - return ( - this.env.pos.config.iface_floorplan && - !stayPaymentScreen && - this.pos.mainScreen.name !== "FloorScreen" - ); - }, - __showScreen() { - this._super(...arguments); - this._setIdleTimer(); - }, - /** - * @override - * Before closing pos, we remove the event listeners set on window - * for detecting activities outside FloorScreen. - */ - async _closePos() { - if (IDLE_TIMER_SETTER) { - for (const event of NON_IDLE_EVENTS) { - window.removeEventListener(event, IDLE_TIMER_SETTER); - } - } - await this._super(...arguments); - }, }); diff --git a/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js b/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js index da38c67cc8b..6cc72648f05 100644 --- a/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js +++ b/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js @@ -15,7 +15,7 @@ patch(TicketButton.prototype, "pos_restaurant.TicketButton", { await this.env.pos._syncAllOrdersFromServer(); } finally { this.env.pos.setLoadingOrderState(false); - this.showScreen("TicketScreen"); + this.pos.showScreen("TicketScreen"); } } else { this._super(...arguments); diff --git a/addons/pos_restaurant/static/src/js/Popups/PosPopupController.js b/addons/pos_restaurant/static/src/js/Popups/PosPopupController.js deleted file mode 100644 index 7dd2a06613f..00000000000 --- a/addons/pos_restaurant/static/src/js/Popups/PosPopupController.js +++ /dev/null @@ -1,25 +0,0 @@ -/* @odoo-module */ - -import { PosPopupController } from "@point_of_sale/js/Popups/PosPopupController"; -import { useBus } from "@web/core/utils/hooks"; -import { patch } from "@web/core/utils/patch"; - -patch(PosPopupController.prototype, "pos_restaurant.PosPopupController", { - setup() { - this._super(...arguments); - useBus(this.env.posbus, "close-popups-but-error", this._closePopupsButError); - }, - _closePopupsButError(event) { - const { resolve } = event.detail; - const isErrorPopupOpen = this.popups.some((popup) => - popup.name.toLowerCase().includes("error") - ); - if (!isErrorPopupOpen) { - for (const popup of this.popups) { - popup.props.resolve(false); - } - this.popups.length = 0; // clearing the array but keep the useState - } - resolve(!isErrorPopupOpen); - }, -}); diff --git a/addons/pos_restaurant/static/src/js/Screens/BillScreen.js b/addons/pos_restaurant/static/src/js/Screens/BillScreen.js index 1915a1d9499..ec92e6f0a53 100644 --- a/addons/pos_restaurant/static/src/js/Screens/BillScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/BillScreen.js @@ -4,10 +4,10 @@ import { ReceiptScreen } from "@point_of_sale/js/Screens/ReceiptScreen/ReceiptSc import { registry } from "@web/core/registry"; export class BillScreen extends ReceiptScreen { - static template = "BillScreen"; + static template = "pos_restaurant.BillScreen"; confirm() { this.props.resolve({ confirmed: true, payload: null }); - this.trigger("close-temp-screen"); + this.pos.closeTempScreen(); } whenClosing() { this.confirm(); diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js index 986a31353af..e084378a0a6 100644 --- a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js @@ -11,13 +11,15 @@ import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; import { EditableTable } from "./EditableTable"; import { EditBar } from "./EditBar"; import { TableWidget } from "./TableWidget"; +import { usePos } from "@point_of_sale/app/pos_hook"; +import { useService } from "@web/core/utils/hooks"; const { onPatched, onMounted, onWillUnmount, useRef, useState } = owl; export class FloorScreen extends PosComponent { static components = { EditableTable, EditBar, TableWidget }; static template = "FloorScreen"; - static hideOrderSelector = true; + static storeOnOrder = false; /** * @param {Object} props @@ -25,6 +27,8 @@ export class FloorScreen extends PosComponent { */ setup() { super.setup(); + this.pos = usePos(); + this.popup = useService("popup"); const floor = this.props.floor ? this.props.floor : this.env.pos.floors[0]; this.state = useState({ selectedFloorId: floor.id, @@ -205,7 +209,7 @@ export class FloorScreen extends PosComponent { await this.env.pos.setTable(table); } const order = this.env.pos.get_order(); - this.showScreen(order.get_screen_data().name); + this.pos.showScreen(order.get_screen_data().name); } } async onSaveTable(table) { @@ -231,7 +235,7 @@ export class FloorScreen extends PosComponent { if (!selectedTable) { return; } - const { confirmed, payload: newName } = await this.showPopup(TextInputPopup, { + const { confirmed, payload: newName } = await this.popup.add(TextInputPopup, { startingValue: selectedTable.name, title: this.env._t("Table Name ?"), }); @@ -248,7 +252,7 @@ export class FloorScreen extends PosComponent { if (!selectedTable) { return; } - const { confirmed, payload: inputNumber } = await this.showPopup(NumberPopup, { + const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, { startingValue: selectedTable.seats, cheap: true, title: this.env._t("Number of Seats ?"), @@ -289,7 +293,7 @@ export class FloorScreen extends PosComponent { if (!this.selectedTable) { return; } - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Are you sure ?"), body: this.env._t("Removing a table cannot be undone"), }); diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js index 6d53dbbdedc..454a7b7a55c 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { TextAreaPopup } from "@point_of_sale/js/Popups/TextAreaPopup"; export class OrderlineNoteButton extends PosComponent { @@ -10,6 +10,7 @@ export class OrderlineNoteButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("click", this.onClick); } get selectedOrderline() { @@ -20,7 +21,7 @@ export class OrderlineNoteButton extends PosComponent { return; } - const { confirmed, payload: inputNote } = await this.showPopup(TextAreaPopup, { + const { confirmed, payload: inputNote } = await this.popup.add(TextAreaPopup, { startingValue: this.selectedOrderline.get_note(), title: this.env._t("Add Internal Note"), }); diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js index a22f8ef785f..a5344c0f6f7 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js @@ -1,5 +1,6 @@ /** @odoo-module */ +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { useListener } from "@web/core/utils/hooks"; @@ -9,6 +10,7 @@ export class PrintBillButton extends PosComponent { setup() { super.setup(); + this.pos = usePos(); useListener("click", this.onClick); } _isDisabled() { @@ -16,7 +18,7 @@ export class PrintBillButton extends PosComponent { return order.get_orderlines().length === 0; } onClick() { - this.showTempScreen("BillScreen"); + this.pos.showTempScreen("BillScreen"); } } diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js index 0b687865e97..6e8564a597d 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js @@ -1,5 +1,6 @@ /** @odoo-module */ +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { useListener } from "@web/core/utils/hooks"; @@ -9,6 +10,7 @@ export class SplitBillButton extends PosComponent { setup() { super.setup(); + this.pos = usePos(); useListener("click", this.onClick); } _isDisabled() { @@ -20,7 +22,7 @@ export class SplitBillButton extends PosComponent { ); } async onClick() { - this.showScreen("SplitBillScreen"); + this.pos.showScreen("SplitBillScreen"); } } diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js index 63b58318d86..b4715d343c0 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js @@ -3,6 +3,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { useService } from "@web/core/utils/hooks"; /** * IMPROVEMENT: Perhaps this class is quite complicated for its worth. @@ -15,6 +16,7 @@ export class SubmitOrderButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); this.clicked = false; //mutex, we don't want to be able to spam the printers } async _onClick() { @@ -27,7 +29,7 @@ export class SubmitOrderButton extends PosComponent { if (isPrintSuccessful) { order.updatePrintedResume(); } else { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Printing failed"), body: this.env._t("Failed in printing the changes in the order"), }); diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js index 9d9c2525583..5d72d83c83b 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js @@ -2,7 +2,7 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; @@ -11,6 +11,7 @@ export class TableGuestsButton extends PosComponent { setup() { super.setup(); + this.popup = useService("popup"); useListener("click", this.onClick); } get currentOrder() { @@ -20,7 +21,7 @@ export class TableGuestsButton extends PosComponent { return this.currentOrder ? this.currentOrder.getCustomerCount() : 0; } async onClick() { - const { confirmed, payload: inputNumber } = await this.showPopup(NumberPopup, { + const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, { startingValue: this.nGuests, cheap: true, title: this.env._t("Guests ?"), @@ -32,7 +33,7 @@ export class TableGuestsButton extends PosComponent { // Set the maximum number possible for an integer const max_capacity = 2 ** 31 - 1; if (guestCount > max_capacity) { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: this.env._t("Blocked action"), body: _.str.sprintf( this.env._t("You cannot put a number that exceeds %s "), diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js index 748781b4f12..f2058b302bc 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js @@ -1,5 +1,6 @@ /** @odoo-module */ +import { usePos } from "@point_of_sale/app/pos_hook"; import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { useListener } from "@web/core/utils/hooks"; @@ -9,11 +10,12 @@ export class TransferOrderButton extends PosComponent { setup() { super.setup(); + this.pos = usePos(); useListener("click", this.onClick); } async onClick() { this.env.pos.setCurrentOrderToTransfer(); - this.showScreen("FloorScreen"); + this.pos.showScreen("FloorScreen"); } } diff --git a/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js index 74b471caa69..6707a32d020 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js @@ -1,9 +1,10 @@ /** @odoo-module */ import { ReceiptScreen } from "@point_of_sale/js/Screens/ReceiptScreen/ReceiptScreen"; -import { usePos } from "@point_of_sale/app/pos_store"; +import { usePos } from "@point_of_sale/app/pos_hook"; import { patch } from "@web/core/utils/patch"; import { onWillUnmount } from "@odoo/owl"; +import { FloorScreen } from "@pos_restaurant/js/Screens/FloorScreen/FloorScreen"; patch(ReceiptScreen, "pos_restaurant.ReceiptScreen", { showBackToFloorButton: true, @@ -15,7 +16,7 @@ patch(ReceiptScreen.prototype, "pos_restaurant.ReceiptScreen", { this.pos = usePos(); onWillUnmount(() => { // When leaving the receipt screen to the floor screen the order is paid and can be removed - if (this.pos.mainScreen.name === "FloorScreen") { + if (this.pos.mainScreen.component === FloorScreen) { this.env.pos.removeOrder(this.currentOrder); } }); diff --git a/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js index 64918e48f42..e3c3fa54e64 100644 --- a/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js @@ -6,6 +6,7 @@ import { Order } from "@point_of_sale/js/models"; import { SplitOrderline } from "./SplitOrderline"; import { registry } from "@web/core/registry"; +import { usePos } from "@point_of_sale/app/pos_hook"; const { useState, onMounted } = owl; @@ -15,6 +16,7 @@ export class SplitBillScreen extends PosComponent { setup() { super.setup(); + this.pos = usePos(); useListener("click-line", this.onClickLine); this.splitlines = useState(this._initSplitLines(this.env.pos.get_order())); this.newOrderLines = {}; @@ -46,7 +48,7 @@ export class SplitBillScreen extends PosComponent { this._updateNewOrder(line); } back() { - this.showScreen("ProductScreen"); + this.pos.showScreen("ProductScreen"); } proceed() { if (_.isEmpty(this.splitlines)) { @@ -82,7 +84,7 @@ export class SplitBillScreen extends PosComponent { this.env.pos.orders.add(reactiveNewOrder); this.env.pos.selectedOrder = reactiveNewOrder; } - this.showScreen("PaymentScreen"); + this.pos.showScreen("PaymentScreen"); } /** * @param {models.Order} order diff --git a/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js b/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js index 59cfa723caf..3eb05f7d6cf 100644 --- a/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js @@ -16,9 +16,9 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", { const order = this.env.pos.get_order(); if (order) { const { name: screenName } = order.get_screen_data(); - this.showScreen(screenName); + this.pos.showScreen(screenName); } else { - this.showScreen("FloorScreen"); + this.pos.showScreen("FloorScreen"); } } }, @@ -101,7 +101,7 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", { ? this.env.pos.getTableOrders(this.env.pos.table.id) : this.env.pos.orders; if (orderList.length == 0) { - this.showScreen("FloorScreen"); + this.pos.showScreen("FloorScreen"); } } }, @@ -135,7 +135,7 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", { this.env.pos.removeOrder(order); return true; } catch { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: "Failed to set tip", body: `Failed to set tip to ${order.name}. Do you want to proceed on setting the tips of the remaining?`, }); diff --git a/addons/pos_restaurant/static/src/js/Screens/TipScreen.js b/addons/pos_restaurant/static/src/js/Screens/TipScreen.js index 0e195e30e51..be0c6ddbf4b 100644 --- a/addons/pos_restaurant/static/src/js/Screens/TipScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/TipScreen.js @@ -6,6 +6,8 @@ import { renderToString } from "@web/core/utils/render"; import { registry } from "@web/core/registry"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; +import { usePos } from "@point_of_sale/app/pos_hook"; +import { useService } from "@web/core/utils/hooks"; const { onMounted } = owl; @@ -14,6 +16,8 @@ export class TipScreen extends PosComponent { static showBackToFloorButton = true; setup() { super.setup(); + this.pos = usePos(); + this.popup = useService("popup"); this.state = this.currentOrder.uiState.TipScreen; this._totalAmount = this.currentOrder.get_total_with_tax(); @@ -47,7 +51,7 @@ export class TipScreen extends PosComponent { const serverId = this.env.pos.validated_orders_name_server_id_map[order.name]; if (!serverId) { - this.showPopup(ErrorPopup, { + this.popup.add(ErrorPopup, { title: this.env._t("Unsynced order"), body: this.env._t( "This order is not yet synced to server. Make sure it is synced then try again." @@ -67,7 +71,7 @@ export class TipScreen extends PosComponent { } if (amount > 0.25 * this.totalAmount) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: "Are you sure?", body: `${this.env.pos.format_currency( amount @@ -104,7 +108,7 @@ export class TipScreen extends PosComponent { this.env.pos.add_new_order(); } const { name, props } = this.nextScreen; - this.showScreen(name, props); + this.pos.showScreen(name, props); } get nextScreen() { if (this.env.pos.config.module_pos_restaurant && this.env.pos.config.iface_floorplan) { @@ -139,7 +143,7 @@ export class TipScreen extends PosComponent { async _printIoT(receipt) { const printResult = await this.env.proxy.printer.print_receipt(receipt); if (!printResult.successful) { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: printResult.message.title, body: printResult.message.body, }); @@ -151,7 +155,7 @@ export class TipScreen extends PosComponent { $(this.el).find(".pos-receipt-container").html(receipt); window.print(); } catch { - await this.showPopup(ErrorPopup, { + await this.popup.add(ErrorPopup, { title: this.env._t("Printing is not supported on some browsers"), body: this.env._t( "Printing is not supported on some browsers due to no default printing protocol " + diff --git a/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml index 14743a187b1..b1cbe54b857 100644 --- a/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml +++ b/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml @@ -1,7 +1,7 @@ - +
diff --git a/addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml index 688bcbcd58f..d35b22410f2 100644 --- a/addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml +++ b/addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml @@ -6,7 +6,7 @@
- + Back diff --git a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js index c99b9c23b7e..ec7d0f9ab5e 100644 --- a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js +++ b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js @@ -3,7 +3,7 @@ import { Gui } from "@point_of_sale/js/Gui"; const { EventBus } = owl; - +// FIXME POSREF: should probably be a service instead of a singleton. class SaleOrderFetcher extends EventBus { constructor() { super(); diff --git a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js index d58be411e26..3157508d0b4 100644 --- a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js +++ b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js @@ -2,10 +2,9 @@ import { sprintf } from "web.utils"; import { parse } from "web.field_utils"; -import { useListener } from "@web/core/utils/hooks"; +import { useListener, useService } from "@web/core/utils/hooks"; import { registry } from "@web/core/registry"; import { ControlButtonsMixin } from "@point_of_sale/js/ControlButtonsMixin"; -import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer"; import { saleOrderFetcher } from "@pos_sale/js/OrderManagementScreen/SaleOrderFetcher"; import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen"; import { orderManagement } from "@point_of_sale/js/PosContext"; @@ -24,10 +23,11 @@ const { onMounted, onWillUnmount, useState } = owl; export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentToOrderScreen) { static components = { SaleOrderList, SaleOrderManagementControlPanel }; static template = "SaleOrderManagementScreen"; - static hideOrderSelector = true; setup() { super.setup(); + this.popup = useService("popup"); + this.numberBuffer = useService("number_buffer"); useListener("close-screen", this.close); useListener("click-sale-order", this._onClickSaleOrder); useListener("next-page", this._onNextPage); @@ -72,7 +72,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo async _setNumpadMode(event) { const { mode } = event.detail; this.numpadMode = mode; - numberBuffer.reset(); + this.numberBuffer.reset(); } _onNextPage() { saleOrderFetcher.nextPage(); @@ -87,7 +87,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo } async _onClickSaleOrder(event) { const clickedOrder = event.detail; - const { confirmed, payload: selectedOption } = await this.showPopup(SelectionPopup, { + const { confirmed, payload: selectedOption } = await this.popup.add(SelectionPopup, { title: this.env._t("What do you want to do?"), list: [ { id: "0", label: this.env._t("Apply a down payment"), item: false }, @@ -116,7 +116,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo this.env._t("There was a problem in loading the %s customer."), sale_order.partner_id[1] ); - await this.showPopup(ErrorPopup, { title, body }); + await this.popup.add(ErrorPopup, { title, body }); } currentPOSOrder.set_partner( this.env.pos.db.get_partner_by_id(sale_order.partner_id[0]) @@ -146,7 +146,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo .filter((line) => !this.env.pos.db.get_product_by_id(line.product_id[0])) .map((line) => line.product_id[0]); if (product_to_add_in_pos.length) { - const { confirmed } = await this.showPopup(ConfirmPopup, { + const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Products not available in POS"), body: this.env._t( "Some of the products in your Sale Order are not available in POS, do you want to import them?" @@ -202,7 +202,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo // Ask once when `useLoadedLots` is undefined, then reuse it's value on the succeeding lines. const { confirmed } = useLoadedLots === undefined - ? await this.showPopup(ConfirmPopup, { + ? await this.popup.add(ConfirmPopup, { title: this.env._t("SN/Lots Loading"), body: this.env._t( "Do you want to load the SN/Lots linked to the Sales Order?" @@ -244,7 +244,9 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo this.env.pos.config.down_payment_product_id[0] ); if (!down_payment_product) { - await this.env.pos._addProducts([this.env.pos.config.down_payment_product_id[0]]); + await this.env.pos._addProducts([ + this.env.pos.config.down_payment_product_id[0], + ]); down_payment_product = this.env.pos.db.get_product_by_id( this.env.pos.config.down_payment_product_id[0] ); @@ -260,7 +262,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo down_payment = sale_order.amount_total; } - const { confirmed, payload } = await this.showPopup(NumberPopup, { + const { confirmed, payload } = await this.popup.add(NumberPopup, { title: sprintf( this.env._t("Percentage of %s"), this.env.pos.format_currency(sale_order.amount_total) @@ -291,7 +293,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo "It seems that you didn't configure a down payment product in your point of sale.\ You can go to your point of sale configuration to choose one." ); - await this.showPopup(ErrorPopup, { title, body }); + await this.popup.add(ErrorPopup, { title, body }); } } diff --git a/addons/pos_sale/static/src/js/SetSaleOrderButton.js b/addons/pos_sale/static/src/js/SetSaleOrderButton.js index 5b73ee8a727..f86ff3e7dc0 100644 --- a/addons/pos_sale/static/src/js/SetSaleOrderButton.js +++ b/addons/pos_sale/static/src/js/SetSaleOrderButton.js @@ -3,13 +3,14 @@ import { PosComponent } from "@point_of_sale/js/PosComponent"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { useListener } from "@web/core/utils/hooks"; -import { Gui } from "@point_of_sale/js/Gui"; +import { usePos } from "@point_of_sale/app/pos_hook"; export class SetSaleOrderButton extends PosComponent { static template = "SetSaleOrderButton"; setup() { super.setup(); + this.pos = usePos(); useListener("click", this.onClick); } get currentOrder() { @@ -25,13 +26,10 @@ export class SetSaleOrderButton extends PosComponent { args: [[]], kwargs: { context: this.env.session.user_context }, }); - // LegacyComponent doesn't work the same way as before. - // We need to use Gui here to show the screen. This will work - // because ui methods in Gui is bound to the root component. const screen = this.env.isMobile ? "MobileSaleOrderManagementScreen" : "SaleOrderManagementScreen"; - Gui.showScreen(screen); + this.pos.showScreen(screen); } } diff --git a/addons/pos_sale_product_configurator/static/src/js/models.js b/addons/pos_sale_product_configurator/static/src/js/models.js index c48a3b2af28..0474e30ab0e 100644 --- a/addons/pos_sale_product_configurator/static/src/js/models.js +++ b/addons/pos_sale_product_configurator/static/src/js/models.js @@ -1,6 +1,5 @@ /** @odoo-module */ -import { Gui } from "@point_of_sale/js/Gui"; import { Order } from "@point_of_sale/js/models"; import { patch } from "@web/core/utils/patch"; @@ -21,7 +20,7 @@ patch(Order.prototype, "pos_sale_product_configurator.Order", { if (isProductLoaded) { const quantity = this.get_selected_orderline().get_quantity(); const info = await this.pos.getProductInfo(product, quantity); - Gui.showPopup(ProductInfoPopup, { info: info, product: product }); + this.pos.env.services.popup.add(ProductInfoPopup, { info: info, product: product }); } } }, diff --git a/addons/pos_six/static/src/js/payment_six.js b/addons/pos_six/static/src/js/payment_six.js index 7e0e73dc8f2..855fdef930b 100644 --- a/addons/pos_six/static/src/js/payment_six.js +++ b/addons/pos_six/static/src/js/payment_six.js @@ -1,7 +1,6 @@ /** @odoo-module */ /* global timapi */ -import { Gui } from "@point_of_sale/js/Gui"; import core from "web.core"; import { PaymentInterface } from "@point_of_sale/js/payment"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; @@ -119,7 +118,7 @@ export const PaymentSix = PaymentInterface.extend({ if (event.exception) { if (event.exception.resultCode !== timapi.constants.ResultCode.apiCancelEcr) { - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: _t("Transaction was not processed correctly"), body: event.exception.errorText, }); @@ -142,7 +141,7 @@ export const PaymentSix = PaymentInterface.extend({ _onBalanceComplete: function (event, data) { if (event.exception) { - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: _t("Balance Failed"), body: _t("The balance operation failed."), }); diff --git a/addons/pos_stripe/static/src/js/payment_stripe.js b/addons/pos_stripe/static/src/js/payment_stripe.js index e01c0dedf09..30e585024e7 100644 --- a/addons/pos_stripe/static/src/js/payment_stripe.js +++ b/addons/pos_stripe/static/src/js/payment_stripe.js @@ -4,7 +4,6 @@ import core from "web.core"; import rpc from "web.rpc"; import { PaymentInterface } from "@point_of_sale/js/payment"; -import { Gui } from "@point_of_sale/js/Gui"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; const _t = core._t; @@ -142,8 +141,10 @@ export const PaymentStripe = PaymentInterface.extend({ captureAfterPayment: async function (processPayment, line) { const capturePayment = await this.capturePayment(processPayment.paymentIntent.id); - if (capturePayment.charges) - {line.card_type = capturePayment.charges.data[0].payment_method_details.card_present.brand;} + if (capturePayment.charges) { + line.card_type = + capturePayment.charges.data[0].payment_method_details.card_present.brand; + } line.transaction_id = capturePayment.id; }, @@ -240,7 +241,7 @@ export const PaymentStripe = PaymentInterface.extend({ if (!title) { title = _t("Stripe Error"); } - Gui.showPopup(ErrorPopup, { + this.pos.env.services.popup.add(ErrorPopup, { title: title, body: msg, });