From 8d35700aafd95f0a42785b599088ac83fb541e3f Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Thu, 30 Mar 2023 06:30:33 +0000 Subject: [PATCH] [REF] point_of_sale, pos_restaurant, *: stop using legacy environment *: web This commit removes the use of the legacy environment from the PoS, as it is no longer needed. This will allow us to more easily use the new unit testing infrastructure from web. Part-of: odoo/odoo#117290 --- .../static/src/app/number_buffer_service.js | 27 +- .../point_of_sale/static/src/app/pos_store.js | 88 +++--- .../point_of_sale/static/src/css/chrome50.css | 6 - addons/point_of_sale/static/src/js/Chrome.js | 257 ++++-------------- .../static/src/js/ControlButtonsMixin.js | 14 +- .../static/src/js/Popups/ClosePosPopup.js | 8 +- .../static/src/js/Popups/NumberPopup.js | 4 +- .../js/Screens/PaymentScreen/PaymentScreen.js | 5 +- .../PaymentScreen/PaymentScreenNumpad.js | 3 +- .../js/Screens/ProductScreen/NumpadWidget.js | 3 +- .../ControlButtons/InvoiceButton.js | 5 +- addons/point_of_sale/static/src/js/models.js | 93 ++++--- addons/point_of_sale/static/src/js/utils.js | 4 +- .../point_of_sale/static/src/xml/Chrome.xml | 4 +- .../tests/unit/number_buffer_service_tests.js | 1 - .../static/src/js/PaymentScreen.js | 8 +- addons/pos_restaurant/static/src/js/models.js | 2 +- .../src/core/l10n/localization_service.js | 1 + .../web/static/tests/helpers/mock_services.js | 1 + 19 files changed, 184 insertions(+), 350 deletions(-) delete mode 100644 addons/point_of_sale/static/src/css/chrome50.css diff --git a/addons/point_of_sale/static/src/app/number_buffer_service.js b/addons/point_of_sale/static/src/app/number_buffer_service.js index 761422227a6..ba2ef7e9861 100644 --- a/addons/point_of_sale/static/src/app/number_buffer_service.js +++ b/addons/point_of_sale/static/src/app/number_buffer_service.js @@ -2,9 +2,8 @@ import { parse } from "web.field_utils"; import { barcodeService } from "@barcodes/barcode_service"; -import { _t } from "web.core"; import { registry } from "@web/core/registry"; -import { EventBus, onWillDestroy, useComponent, useExternalListener } from "@odoo/owl"; +import { EventBus, onWillDestroy, useComponent } from "@odoo/owl"; const INPUT_KEYS = new Set( ["Delete", "Backspace", "+1", "+2", "+5", "+10", "+20", "+50"].concat( @@ -36,7 +35,6 @@ const getDefaultConfig = () => ({ * * Usage * ===== - * - Activate in the construction of root component. `NumberBuffer.activate()` * - Use the buffer in a child component by calling `NumberBuffer.use()` * in the constructor of the child component. * - The component that `uses` the buffer has access to the following instance @@ -58,11 +56,17 @@ const getDefaultConfig = () => ({ * - Write more integration tests. NumberPopup can be used as test component. */ class NumberBuffer extends EventBus { - constructor({ sound }) { + static serviceDependencies = ["sound", "localization"]; + constructor() { super(); + this.setup(...arguments); + } + setup({ sound, localization }) { this.isReset = false; this.bufferHolderStack = []; this.sound = sound; + this.defaultDecimalPoint = localization.decimalPoint; + window.addEventListener("keyup", this._onKeyboardInput.bind(this)); } /** * @returns {String} value of the buffer, e.g. '-95.79' @@ -107,15 +111,6 @@ class NumberBuffer extends EventBus { getFloat() { return parse.float(this.get()); } - /** - * Add keyup listener to window via the useExternalListener hook. - * When the component calling this is unmounted, the listener is also - * removed from window. - */ - activate() { - this.defaultDecimalPoint = _t.database.parameters.decimal_point; - useExternalListener(window, "keyup", this._onKeyboardInput.bind(this)); - } /** * @param {Object} config Use to setup the buffer * @param {String|null} config.decimalPoint The decimal character. @@ -324,9 +319,9 @@ class NumberBuffer extends EventBus { } export const numberBuffer = { - dependencies: ["sound"], - start(env, { sound }) { - return new NumberBuffer({ sound }); + dependencies: NumberBuffer.serviceDependencies, + start(env, deps) { + return new NumberBuffer(deps); }, }; 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 d20667ec66a..748d3429631 100644 --- a/addons/point_of_sale/static/src/app/pos_store.js +++ b/addons/point_of_sale/static/src/app/pos_store.js @@ -1,15 +1,14 @@ /** @odoo-module */ import { PosGlobalState } from "@point_of_sale/js/models"; -import legacyEnv from "web.env"; import { registry } from "@web/core/registry"; import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; -import { reactive, markRaw } from "@odoo/owl"; import { Reactive } from "@point_of_sale/utils"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { _t } from "@web/core/l10n/translation"; import { CashOpeningPopup } from "@point_of_sale/js/Popups/CashOpeningPopup"; +import { sprintf } from "@web/core/utils/strings"; export class PosStore extends Reactive { /** @type {'LOADING' | 'READY' | 'CLOSING'} */ @@ -18,7 +17,6 @@ export class PosStore extends Reactive { loadingSkipButtonIsShown = false; mainScreen = { name: null, component: null }; tempScreen = null; - legacyEnv = legacyEnv; static serviceDependencies = [ "popup", @@ -32,16 +30,16 @@ export class PosStore extends Reactive { this.setup(...arguments); } // use setup instead of constructor because setup can be patched. - setup({ popup, orm, number_buffer, hardware_proxy, barcode_reader }) { + setup(env, { popup, orm, number_buffer, hardware_proxy, barcode_reader }) { this.orm = orm; this.popup = popup; this.numberBuffer = number_buffer; this.barcodeReader = barcode_reader; + this.globalState = new PosGlobalState({ orm, env, hardwareProxy: hardware_proxy }); this.hardwareProxy = hardware_proxy; - this.globalState = new PosGlobalState({ - env: markRaw(legacyEnv), - hardwareProxy: hardware_proxy, - }); + // FIXME POSREF: the hardwareProxy needs the pos and the pos needs the hardwareProxy. Maybe + // the hardware proxy should just be part of the pos service? + this.hardwareProxy.pos = this.globalState; } showScreen(name, props) { @@ -77,7 +75,7 @@ export class PosStore extends Reactive { // FIXME POSREF this looks like it's dead code. reject({ title: _t("HTTPS connection to IoT Box failed"), - body: _.str.sprintf( + body: sprintf( _t( "Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box." ), @@ -100,40 +98,38 @@ export class PosStore extends Reactive { window.location = "/web#action=point_of_sale.action_client_pos_menu"; } - if (this.globalState.db.get_orders().length) { - // If there are orders in the db left unsynced, we try to sync. - // If sync successful, close without asking. - // Otherwise, ask again saying that some orders are not yet synced. - try { - await this.globalState.push_orders(); + // If there are orders in the db left unsynced, we try to sync. + // If sync successful, close without asking. + // Otherwise, ask again saying that some orders are not yet synced. + try { + await this.globalState.push_orders(); + window.location = "/web#action=point_of_sale.action_client_pos_menu"; + } catch (error) { + console.warn(error); + const reason = this.globalState.failed + ? _t( + "Some orders could not be submitted to " + + "the server due to configuration errors. " + + "You can exit the Point of Sale, but do " + + "not close the session before the issue " + + "has been resolved." + ) + : _t( + "Some orders could not be submitted to " + + "the server due to internet connection issues. " + + "You can exit the Point of Sale, but do " + + "not close the session before the issue " + + "has been resolved." + ); + const { confirmed } = await this.popup.add(ConfirmPopup, { + title: _t("Offline Orders"), + body: reason, + }); + if (confirmed) { + // FIXME POSREF setting the location prevents the next render, the loading screen never shows + this.globalState.uiState = "CLOSING"; + this.globalState.loadingSkipButtonIsShown = false; window.location = "/web#action=point_of_sale.action_client_pos_menu"; - } catch (error) { - console.warn(error); - const reason = this.globalState.failed - ? _t( - "Some orders could not be submitted to " + - "the server due to configuration errors. " + - "You can exit the Point of Sale, but do " + - "not close the session before the issue " + - "has been resolved." - ) - : _t( - "Some orders could not be submitted to " + - "the server due to internet connection issues. " + - "You can exit the Point of Sale, but do " + - "not close the session before the issue " + - "has been resolved." - ); - const { confirmed } = await this.popup.add(ConfirmPopup, { - title: _t("Offline Orders"), - body: reason, - }); - if (confirmed) { - // FIXME POSREF setting the location prevents the next render, the loading screen never shows - this.globalState.uiState = "CLOSING"; - this.globalState.loadingSkipButtonIsShown = false; - window.location = "/web#action=point_of_sale.action_client_pos_menu"; - } } } } @@ -146,9 +142,9 @@ export class PosStore extends Reactive { const currentPartner = currentOrder.get_partner(); if (currentPartner && currentOrder.getHasRefundLines()) { this.popup.add(ErrorPopup, { - title: this.env._t("Can't change customer"), - body: _.str.sprintf( - this.env._t( + title: _t("Can't change customer"), + body: sprintf( + _t( "This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer." ), currentPartner.name @@ -218,7 +214,7 @@ export class PosStore extends Reactive { export const posService = { dependencies: PosStore.serviceDependencies, start(env, deps) { - return reactive(new PosStore(deps)); + return new PosStore(env, deps); }, }; diff --git a/addons/point_of_sale/static/src/css/chrome50.css b/addons/point_of_sale/static/src/css/chrome50.css deleted file mode 100644 index 67ecddccfde..00000000000 --- a/addons/point_of_sale/static/src/css/chrome50.css +++ /dev/null @@ -1,6 +0,0 @@ -.pos .screen .content-cell{ - height: 100%; -} -.pos .subwindow .subwindow-container{ - height: 100%; -} \ No newline at end of file diff --git a/addons/point_of_sale/static/src/js/Chrome.js b/addons/point_of_sale/static/src/js/Chrome.js index e04ee9b229a..26f37266ff3 100644 --- a/addons/point_of_sale/static/src/js/Chrome.js +++ b/addons/point_of_sale/static/src/js/Chrome.js @@ -1,242 +1,112 @@ /** @odoo-module */ -import { loadCSS } from "@web/core/assets"; import { useService } from "@web/core/utils/hooks"; import { batched } from "@point_of_sale/js/utils"; -import { debounce } from "@web/core/utils/timing"; +import { throttleForAnimation } from "@web/core/utils/timing"; import { Transition } from "@web/core/transition"; import { MainComponentsContainer } from "@web/core/main_components_container"; -import { WithEnv, ErrorHandler } from "@web/core/utils/components"; +import { ErrorHandler } from "@web/core/utils/components"; import { Navbar } from "@point_of_sale/app/navbar/navbar"; - -// ChromeAdapter imports -import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { registry } from "@web/core/registry"; -import env from "web.env"; - -import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup"; - -import { - onMounted, - onWillDestroy, - useExternalListener, - useSubEnv, - reactive, - onWillUnmount, - Component, -} from "@odoo/owl"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { useExternalListener, useSubEnv, reactive, onWillUnmount, Component } from "@odoo/owl"; /** * Chrome is the root component of the PoS App. */ export class Chrome extends Component { static template = "Chrome"; // FIXME POSREF namespace templates - static components = { Transition, MainComponentsContainer, WithEnv, ErrorHandler, Navbar }; + static components = { Transition, MainComponentsContainer, ErrorHandler, Navbar }; setup() { this.pos = usePos(); this.popup = useService("popup"); - // BEGIN ChromeAdapter - ProductScreen.sortControlButtons(); - const legacyActionManager = useService("legacy_action_manager"); - - this.batchedCustomerDisplayRender = batched(() => { - reactivePos.send_current_order_to_customer_facing_display(); - }); - const reactivePos = reactive(this.pos.globalState, this.batchedCustomerDisplayRender); - env.pos = reactivePos; - env.legacyActionManager = legacyActionManager; - - // The proxy requires the instance of PosGlobalState to function properly. - this.hardwareProxy = useService("hardware_proxy"); - this.hardwareProxy.pos = reactivePos; + const reactivePos = reactive(this.pos.globalState); // TODO: Should we continue on exposing posmodel as global variable? - // Expose only the reactive version of `pos` when in debug mode. - window.posmodel = this.pos.globalState.debug ? reactivePos : this.pos.globalState; - - this.wowlEnv = this.env; - // FIXME POSREF: make wowl services available in legacy env - Object.setPrototypeOf(env.services, this.wowlEnv.services); - this.env = env; - this.__owl__.childEnv = env; + window.posmodel = reactivePos; + // FIXME POSREF: remove useSubEnv({ get isMobile() { return window.innerWidth <= 768; }, - }); - let currentIsMobile = this.env.isMobile; - const updateUI = debounce(() => { - if (this.env.isMobile !== currentIsMobile) { - currentIsMobile = this.env.isMobile; - this.render(true); - } - }, 15); // FIXME POSREF use throttleForAnimation? - useExternalListener(window, "resize", updateUI); - onWillUnmount(updateUI.cancel); - // END ChromeAdapter - - super.setup(); - useExternalListener(window, "beforeunload", this._onBeforeUnload); - useService("number_buffer").activate(); - - useSubEnv({ pos: reactive( - this.env.pos, + reactivePos, batched(() => this.render(true)) // FIXME POSREF remove render(true) ), }); - - onMounted(() => { - // remove default webclient handlers that induce click delay - // FIXME POSREF if these handlers shouldn't be there we should not load the files that add them. - $(document).off(); - $(window).off(); - $("html").off(); - $("body").off(); + let currentIsMobile = this.env.isMobile; + const updateUI = throttleForAnimation(() => { + if (this.env.isMobile !== currentIsMobile) { + currentIsMobile = this.env.isMobile; + // FIXME POSREF + this.render(true); + } }); + useExternalListener(window, "resize", updateUI); + onWillUnmount(updateUI.cancel); - onWillDestroy(() => { - try { - // FIXME POSREF is this needed? - this.env.pos.destroy(); - } catch { - // throwing here causes loop + // prevent backspace from performing a 'back' navigation + document.addEventListener("keydown", (ev) => { + if (ev.key === "Backspace" && !ev.target.matches("input, textarea")) { + ev.preventDefault(); } }); + // This is not done in onWillStart because we want to show the loader immediately this.start(); } + async start() { + await this.pos.globalState.load_server_data(); + if (this.pos.globalState.config.use_proxy) { + await this.pos.connectToProxy(); + } + this._closeOtherTabs(); + this.pos.uiState = "READY"; + const { name, props } = this.startScreen; + this.pos.showScreen(name, props); + this.runBackgroundTasks(); + } // GETTERS // /** * Startup screen can be based on pos config so the startup screen * is only determined after pos data is completely loaded. - * - * NOTE: Wait for pos data to be completed before calling this getter. */ get startScreen() { - if (this.pos.uiState !== "READY") { - console.warn( - `Accessing startScreen of Chrome component before 'pos.uiState' to be 'READY' is not recommended.` - ); - } return { name: "ProductScreen" }; } // CONTROL METHODS // - /** - * Call this function after the Chrome component is mounted. - * This will load pos and assign it to the environment. - */ - async start() { - // Little trick to avoid displaying the block ui during the POS models loading - // FIXME POSREF: use a silent RPC instead - const BlockUiFromRegistry = registry.category("main_components").get("BlockUI"); - registry.category("main_components").remove("BlockUI"); - - try { - await this.env.pos.load_server_data(); - if (this.env.pos.config.use_proxy) { - await this.pos.connectToProxy(); - } - // Load the saved `env.pos.toRefundLines` from localStorage when - // the PosGlobalState is ready. - Object.assign( - this.env.pos.toRefundLines, - this.env.pos.db.load("TO_REFUND_LINES") || {} - ); - this._buildChrome(); - this._closeOtherTabs(); - this.env.pos.selectedCategoryId = - this.env.pos.config.start_category && this.env.pos.config.iface_start_categ_id - ? this.env.pos.config.iface_start_categ_id[0] - : 0; - this.pos.uiState = "READY"; - const { name, props } = this.startScreen; - this.pos.showScreen(name, props); - setTimeout(() => this._runBackgroundTasks()); - } catch (error) { - let title = "Unknown Error"; - let body; - - if (error.message && [100, 200, 404, -32098].includes(error.message.code)) { - // this is the signature of rpc error - if (error.message.code === -32098) { - title = "Network Failure (XmlHttpRequestError)"; - body = - "The Point of Sale could not be loaded due to a network problem.\n" + - "Please check your internet connection."; - } else if (error.message.code === 200) { - title = error.message.data.message || this.env._t("Server Error"); - body = - error.message.data.debug || - this.env._t("The server encountered an error while receiving your order."); - } - } else if (error instanceof Error) { - title = error.message; - if (error.cause) { - body = error.cause.message; - } else { - body = error.stack; - } - } - - return this.popup.add(ErrorTracebackPopup, { title, body, exitButtonIsShown: true }); - } - registry.category("main_components").add("BlockUI", BlockUiFromRegistry); - - // Subscribe to the changes in the models. - this.batchedCustomerDisplayRender(); - } - - _runBackgroundTasks() { + runBackgroundTasks() { // push order in the background, no need to await - this.env.pos.push_orders(); + this.pos.globalState.push_orders(); // Allow using the app even if not all the images are loaded. // Basically, preload the images in the background. this._preloadImages(); - if ( - this.env.pos.config.limited_partners_loading && - this.env.pos.config.partner_load_background - ) { + const { + limited_partners_loading, + partner_load_background, + limited_products_loading, + product_load_background, + } = this.pos.globalState.config; + if (limited_partners_loading && partner_load_background) { // Wrap in fresh reactive: none of the reads during loading should subscribe to anything - reactive(this.env.pos).loadPartnersBackground(); + reactive(this.pos.globalState).loadPartnersBackground(); } - if ( - this.env.pos.config.limited_products_loading && - this.env.pos.config.product_load_background - ) { + if (limited_products_loading && product_load_background) { // Wrap in fresh reactive: none of the reads during loading should subscribe to anything - reactive(this.env.pos) - .loadProductsBackground() - .then(() => { - this.render(true); - }); + reactive(this.pos.globalState).loadProductsBackground(); } } - /** - * Save `env.pos.toRefundLines` in localStorage on beforeunload - closing the - * browser, reloading or going to other page. - */ - _onBeforeUnload() { - this.env.pos.db.save("TO_REFUND_LINES", this.env.pos.toRefundLines); - } - _onSetSyncStatus({ detail: { status, pending } }) { - this.env.pos.synch.status = status; - this.env.pos.synch.pending = pending; - } - // MISC METHODS // _preloadImages() { - for (const product of this.env.pos.db.get_product_by_category(0)) { + for (const product of this.pos.globalState.db.get_product_by_category(0)) { const image = new Image(); image.src = `/web/image?model=product.product&field=image_128&id=${product.id}&unique=${product.write_date}`; } - for (const category of Object.values(this.env.pos.db.category_by_id)) { + for (const category of Object.values(this.pos.globalState.db.category_by_id)) { if (category.id == 0) { continue; } @@ -249,34 +119,15 @@ export class Chrome extends Component { image.src = `/point_of_sale/static/src/img/${imageName}`; } } - - _buildChrome() { - if ($.browser.chrome) { - var chrome_version = $.browser.version.split(".")[0]; - if (parseInt(chrome_version, 10) >= 50) { - loadCSS("/point_of_sale/static/src/css/chrome50.css"); - } - } - - if (this.env.pos.config.iface_big_scrollbars) { - this.pos.hasBigScrollBars = true; - } - - this._disableBackspaceBack(); - } - // prevent backspace from performing a 'back' navigation - _disableBackspaceBack() { - $(document).on("keydown", function (e) { - if (e.which === 8 && !$(e.target).is("input, textarea")) { - e.preventDefault(); - } - }); - } + /** + * Close other tabs that contain the same pos session. + */ _closeOtherTabs() { + // FIXME POSREF use the bus? localStorage["message"] = ""; localStorage["message"] = JSON.stringify({ message: "close_tabs", - session: this.env.pos.pos_session.id, + session: this.pos.globalState.pos_session.id, }); window.addEventListener( @@ -286,7 +137,7 @@ export class Chrome extends Component { const msg = JSON.parse(event.newValue); if ( msg.message === "close_tabs" && - msg.session == this.env.pos.pos_session.id + msg.session == this.pos.globalState.pos_session.id ) { console.info("POS / Session opened in another window. EXITING POS"); this.pos.closePos(); @@ -297,7 +148,7 @@ export class Chrome extends Component { ); } get showCashMoveButton() { - return this.env.pos && this.env.pos.config && this.env.pos.config.cash_control; + return Boolean(this.pos.globalState?.config?.config?.cash_control); } /** * Unmounts the tempScreen on error and dispatches the error in a separate diff --git a/addons/point_of_sale/static/src/js/ControlButtonsMixin.js b/addons/point_of_sale/static/src/js/ControlButtonsMixin.js index 8d76cac4c01..17dc38cbd25 100644 --- a/addons/point_of_sale/static/src/js/ControlButtonsMixin.js +++ b/addons/point_of_sale/static/src/js/ControlButtonsMixin.js @@ -11,7 +11,8 @@ */ export const ControlButtonsMixin = (x) => { const controlButtonsToPosition = []; - const sortedControlButtons = []; + const orderedControlButtons = []; + let sortedControlButtons = []; class Extended extends x { get controlButtons() { @@ -42,10 +43,11 @@ export const ControlButtonsMixin = (x) => { // If no position is set, we just push it to the array. if (!controlButton.position) { - sortedControlButtons.push(controlButton); + orderedControlButtons.push(controlButton); } else { controlButtonsToPosition.push(controlButton); } + Extended.sortControlButtons(); }; /** @@ -56,6 +58,7 @@ export const ControlButtonsMixin = (x) => { * purposes, it is enough. */ Extended.sortControlButtons = function () { + sortedControlButtons = [...orderedControlButtons]; function setControlButton(locator, index, cb) { if (locator == "replace") { sortedControlButtons[index] = cb; @@ -68,7 +71,7 @@ export const ControlButtonsMixin = (x) => { function locate(cb) { const [locator, reference] = cb.position; const index = sortedControlButtons.findIndex((cb) => cb.name == reference); - return [locator, index, reference]; + return [locator, index]; } const cbMissingReference = []; // 1. First pass. If the reference control button isn't there, collect it for second pass. @@ -88,11 +91,8 @@ export const ControlButtonsMixin = (x) => { // ii) They really have missing reference. // Thus, we have to iterate the cb with missing reference in reverse. for (const cb of cbMissingReference.reverse()) { - const [locator, index, reference] = locate(cb); + const [locator, index] = locate(cb); if (index == -1) { - console.warn( - `'${cb.name}' is not properly position because '${reference}' is not found. Is '${reference}' spelled correctly?` - ); sortedControlButtons.push(cb); } else { setControlButton(locator, index, cb); 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 55f49d36b17..f1660e1c911 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -22,8 +22,8 @@ export class ClosePosPopup extends AbstractAwaitablePopup { this.pos = usePos(); this.popup = useService("popup"); this.orm = useService("orm"); + this.action = useService("action"); this.hardwareProxy = useService("hardware_proxy"); - this.legacyActionManager = useService("legacy_action_manager"); this.manualInputCashCount = false; this.cashControl = this.pos.globalState.config.cash_control; this.closeSessionClicked = false; @@ -91,9 +91,9 @@ export class ClosePosPopup extends AbstractAwaitablePopup { } } async downloadSalesReport() { - await this.legacyActionManager.do_action("point_of_sale.sale_details_report", { - additional_context: { - active_ids: [this.pos.globalState.pos_session.id], + await this.action.doAction("point_of_sale.sale_details_report", { + additionalContext: { + active_ids: [this.env.pos.pos_session.id], }, }); } 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 f098c21f95b..e373d5565da 100644 --- a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js @@ -5,7 +5,6 @@ var _t = core._t; import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { useService } from "@web/core/utils/hooks"; import { useState } from "@odoo/owl"; -import { localization } from "@web/core/l10n/localization"; export class NumberPopup extends AbstractAwaitablePopup { static template = "NumberPopup"; @@ -31,6 +30,7 @@ export class NumberPopup extends AbstractAwaitablePopup { */ setup() { super.setup(); + this.localization = useService("localization"); let startingBuffer = ""; if (typeof this.props.startingValue === "number" && this.props.startingValue > 0) { startingBuffer = this.props.startingValue @@ -46,7 +46,7 @@ export class NumberPopup extends AbstractAwaitablePopup { }); } get decimalSeparator() { - return localization.decimalPoint; + return this.localization.decimalPoint; } get inputBuffer() { if (this.state.buffer === null) { 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 88fc7c62dae..f411ec0dc24 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 @@ -30,6 +30,7 @@ export class PaymentScreen extends Component { this.pos = usePos(); this.orm = useService("orm"); this.popup = useService("popup"); + this.action = useService("action"); this.notification = useService("pos_notification"); this.hardwareProxy = useService("hardware_proxy"); this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) => @@ -233,8 +234,8 @@ export class PaymentScreen extends Component { // 2. Invoice. if (this.currentOrder.is_to_invoice()) { if (syncOrderResult.length) { - await this.env.legacyActionManager.do_action("account.account_invoices", { - additional_context: { + await this.action.doAction("account.account_invoices", { + additionalContext: { active_ids: [syncOrderResult[0].account_move], }, }); diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js index 6ed639b62c7..62715e4421e 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js @@ -2,6 +2,7 @@ import { PSNumpadInputButton } from "./PSNumpadInputButton"; import { Component } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; export class PaymentScreenNumpad extends Component { static components = { PSNumpadInputButton }; @@ -9,6 +10,6 @@ export class PaymentScreenNumpad extends Component { setup() { super.setup(); - this.decimalPoint = this.env._t.database.parameters.decimal_point; + this.decimalPoint = useService("localization").decimalPoint; } } diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js index 1e2469d401f..2e5cabc793d 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js @@ -16,6 +16,7 @@ export class NumpadWidget extends Component { }; setup() { this.numberBuffer = useService("number_buffer"); + this.localization = useService("localization"); } get hasPriceControlRights() { return ( @@ -43,6 +44,6 @@ export class NumpadWidget extends Component { this.numberBuffer.sendKey(key); } get decimalSeparator() { - return this.env._t.database.parameters.decimal_point; + return this.localization.decimalPoint; } } 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 a5d8f2df7c2..2e6c84cd1ac 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 @@ -15,6 +15,7 @@ export class InvoiceButton extends Component { this.invoiceButton = useRef("invoice-button"); this.popup = useService("popup"); this.orm = useService("orm"); + this.action = useService("action"); } get isAlreadyInvoiced() { if (!this.props.order) { @@ -38,8 +39,8 @@ export class InvoiceButton extends Component { { load: false } ); if (orderWithInvoice && orderWithInvoice.account_move) { - await this.env.legacyActionManager.do_action("account.account_invoices", { - additional_context: { + await this.action.doAction("account.account_invoices", { + additionalContext: { active_ids: [orderWithInvoice.account_move], }, }); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 857e6926caa..439ee66d5a0 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -193,7 +193,7 @@ export class PosGlobalState extends PosModel { }; } async load_product_uom_unit() { - const uom_id = await this.env.services.orm.call("ir.model.data", "check_object_reference", [ + const uom_id = await this.orm.call("ir.model.data", "check_object_reference", [ "uom", "product_uom_unit", ]); @@ -204,11 +204,18 @@ export class PosGlobalState extends PosModel { await this.load_product_uom_unit(); await this.load_orders(); this.set_start_order(); + Object.assign(this.toRefundLines, this.db.load("TO_REFUND_LINES") || {}); + window.addEventListener("beforeunload", () => + this.db.save("TO_REFUND_LINES", this.toRefundLines) + ); + const { start_category, iface_start_categ_id } = this.config; + this.selectedCategoryId = (start_category && iface_start_categ_id?.[0]) || 0; + this.hasBigScrollBars = this.config.iface_big_scrollbars; this.markReady(); } async load_server_data() { - const loadedData = await this.env.services.orm.call("pos.session", "load_pos_data", [ + const loadedData = await this.orm.silent.call("pos.session", "load_pos_data", [ [odoo.pos_session_id], ]); await this._processData(loadedData); @@ -370,32 +377,28 @@ export class PosGlobalState extends PosModel { // updated partners, and fails if not async load_new_partners() { const search_params = { domain: this.prepare_new_partners_domain() }; - if (this.env.pos.config.limited_partners_loading) { + if (this.config.limited_partners_loading) { 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; + if (this.config.partner_load_background) { + search_params["limit"] = this.config.limited_partners_amount || 1; } else { search_params["limit"] = 1; } } // FIXME POSREF TIMEOUT 3000 - const partners = await this.env.services.orm.silent.call( + const partners = await this.orm.silent.call( "pos.session", "get_pos_ui_res_partner_by_params", [[odoo.pos_session_id], search_params] ); - if (this.env.pos.config.partner_load_background) { + if (this.config.partner_load_background) { this.loadPartnersBackground( search_params["domain"], - this.env.pos.config.limited_partners_amount || 1, + this.config.limited_partners_amount || 1, "write_date desc" ); } - if (this.addPartners(partners)) { - return true; - } else { - return false; - } + return this.addPartners(partners); } setSelectedCategoryId(categoryId) { @@ -557,7 +560,7 @@ export class PosGlobalState extends PosModel { } } } - const products = await this.env.services.orm.call( + const products = await this.orm.call( "pos.session", "get_pos_ui_product_product_by_params", [odoo.pos_session_id, { domain: [["id", "in", [...missingProductIds]]] }] @@ -568,7 +571,7 @@ export class PosGlobalState extends PosModel { async _loadPartners(partnerIds) { if (partnerIds.length > 0) { // FIXME POSREF TIMEOUT - const fetchedPartners = await this.env.services.orm.silent.call( + const fetchedPartners = await this.orm.silent.call( "pos.session", "get_pos_ui_res_partner_by_params", [[odoo.pos_session_id], { domain: [["id", "in", partnerIds]] }] @@ -593,7 +596,7 @@ export class PosGlobalState extends PosModel { let page = 0; let products = []; do { - products = await this.env.services.orm.silent.call( + products = await this.orm.silent.call( "pos.session", "get_pos_ui_product_product_by_params", [ @@ -614,7 +617,7 @@ export class PosGlobalState extends PosModel { let i = 0; let partners = []; do { - partners = await this.env.services.orm.silent.call( + partners = await this.orm.silent.call( "pos.session", "get_pos_ui_res_partner_by_params", [ @@ -642,7 +645,7 @@ export class PosGlobalState extends PosModel { this.set_synch("connecting", removedOrdersIds.length); try { - const removeOrdersResponseData = await this.env.services.orm.silent.call( + const removeOrdersResponseData = await this.orm.silent.call( "pos.order", "remove_from_ui", [removedOrdersIds] @@ -736,7 +739,7 @@ export class PosGlobalState extends PosModel { return message; } async _getOrdersJson() { - return await this.env.services.orm.call("pos.order", "get_draft_share_order_ids", [], { + return await this.orm.call("pos.order", "get_draft_share_order_ids", [], { config_id: this.config.id, }); } @@ -762,11 +765,10 @@ export class PosGlobalState extends PosModel { return message; } async _getPricelistJson(pricelistsToGet) { - return await this.env.services.orm.call( - "pos.session", - "get_pos_ui_product_pricelists_by_ids", - [[odoo.pos_session_id], pricelistsToGet] - ); + return await this.orm.call("pos.session", "get_pos_ui_product_pricelists_by_ids", [ + [odoo.pos_session_id], + pricelistsToGet, + ]); } _addPosPricelists(pricelistsJson) { if (!this.config.use_pricelist) { @@ -805,11 +807,10 @@ export class PosGlobalState extends PosModel { return message; } async _getFiscalPositionJson(fiscalPositionToGet) { - return await this.env.services.orm.call( - "pos.session", - "get_pos_ui_account_fiscal_positions_by_ids", - [[odoo.pos_session_id], fiscalPositionToGet] - ); + return await this.orm.call("pos.session", "get_pos_ui_account_fiscal_positions_by_ids", [ + [odoo.pos_session_id], + fiscalPositionToGet, + ]); } _addPosFiscalPosition(fiscalPositionJson) { this.fiscal_positions.push(...fiscalPositionJson); @@ -830,11 +831,12 @@ export class PosGlobalState extends PosModel { const order = this.get_order(); // check back-end method `get_product_info_pos` to see what it returns // We do this so it's easier to override the value returned and use it in the component template later - const productInfo = await this.env.services.orm.call( - "product.product", - "get_product_info_pos", - [[product.id], product.get_price(order.pricelist, quantity), quantity, this.config.id] - ); + const productInfo = await this.orm.call("product.product", "get_product_info_pos", [ + [product.id], + product.get_price(order.pricelist, quantity), + quantity, + this.config.id, + ]); const priceWithoutTax = productInfo["all_prices"]["price_without_tax"]; const margin = priceWithoutTax - product.standard_price; @@ -865,11 +867,9 @@ export class PosGlobalState extends PosModel { }; } async getClosePosInfo() { - const closingData = await this.env.services.orm.call( - "pos.session", - "get_closing_control_data", - [[this.pos_session.id]] - ); + const closingData = await this.orm.call("pos.session", "get_closing_control_data", [ + [this.pos_session.id], + ]); const ordersDetails = closingData.orders_details; const paymentsAmount = closingData.payments_amount; const payLaterAmount = closingData.pay_later_amount; @@ -1057,7 +1057,7 @@ export class PosGlobalState extends PosModel { } // we try to send the order. silent prevents a spinner if it takes too long. (unless we are sending an invoice, // then we want to notify the user that we are waiting on something ) - const orm = options.to_invoice ? this.env.services.orm : this.env.services.orm.silent; + const orm = options.to_invoice ? this.orm : this.orm.silent; // FIXME POSREF timeout // const timeout = typeof options.timeout === "number" ? options.timeout : 30000 * orders.length; return orm @@ -1555,17 +1555,16 @@ export class PosGlobalState extends PosModel { */ async _addProducts(ids, setAvailable = true) { if (setAvailable) { - await this.env.services.orm.write("product.product", ids, { available_in_pos: true }); + await this.orm.write("product.product", ids, { available_in_pos: true }); } - const product = await this.env.services.orm.call( - "pos.session", - "get_pos_ui_product_product_by_params", - [odoo.pos_session_id, { domain: [["id", "in", ids]] }] - ); + const product = await this.orm.call("pos.session", "get_pos_ui_product_product_by_params", [ + odoo.pos_session_id, + { domain: [["id", "in", ids]] }, + ]); this._loadProductProduct(product); } async refreshTotalDueOfPartner(partner) { - const partnerWithUpdatedTotalDue = await this.env.services.orm.searchRead( + const partnerWithUpdatedTotalDue = await this.orm.searchRead( "res.partner", [["id", "=", partner.id]], ["total_due"] diff --git a/addons/point_of_sale/static/src/js/utils.js b/addons/point_of_sale/static/src/js/utils.js index bf9e15c28fe..499a82d0b7d 100644 --- a/addons/point_of_sale/static/src/js/utils.js +++ b/addons/point_of_sale/static/src/js/utils.js @@ -9,7 +9,7 @@ */ export function batched(callback) { let called = false; - return async () => { + return async (...args) => { // This await blocks all calls to the callback here, then releases them sequentially // in the next microtick. This line decides the granularity of the batch. await Promise.resolve(); @@ -20,7 +20,7 @@ export function batched(callback) { // within the callback will proceed only after resetting called to false, and have // a chance to execute the callback again Promise.resolve().then(() => (called = false)); - callback(); + callback(...args); } }; } diff --git a/addons/point_of_sale/static/src/xml/Chrome.xml b/addons/point_of_sale/static/src/xml/Chrome.xml index 0df3e6e76a8..e20bbdc9b41 100644 --- a/addons/point_of_sale/static/src/xml/Chrome.xml +++ b/addons/point_of_sale/static/src/xml/Chrome.xml @@ -38,9 +38,7 @@ - - - + diff --git a/addons/point_of_sale/static/tests/unit/number_buffer_service_tests.js b/addons/point_of_sale/static/tests/unit/number_buffer_service_tests.js index 82340787c4a..b16b70b09aa 100644 --- a/addons/point_of_sale/static/tests/unit/number_buffer_service_tests.js +++ b/addons/point_of_sale/static/tests/unit/number_buffer_service_tests.js @@ -26,7 +26,6 @@ QUnit.test("simple fast inputs with capture in between", async function (assert) setup() { this.state = useState({ buffer: "" }); this.numberBuffer = useService("number_buffer"); - this.numberBuffer.activate(); this.numberBuffer.use({ state: this.state, }); diff --git a/addons/pos_loyalty/static/src/js/PaymentScreen.js b/addons/pos_loyalty/static/src/js/PaymentScreen.js index e83ceb6790c..4bd3fa5590c 100644 --- a/addons/pos_loyalty/static/src/js/PaymentScreen.js +++ b/addons/pos_loyalty/static/src/js/PaymentScreen.js @@ -156,12 +156,8 @@ patch(PaymentScreen.prototype, "pos_loyalty.PaymentScreen", { } } if (payload.coupon_report) { - for (const report_entry of Object.entries(payload.coupon_report)) { - await this.env.legacyActionManager.do_action(report_entry[0], { - additional_context: { - active_ids: report_entry[1], - }, - }); + for (const [actionId, active_ids] of Object.entries(payload.coupon_report)) { + await this.action.doAction(actionId, { additionalContext: { active_ids } }); } } order.new_coupon_info = payload.new_coupon_info; diff --git a/addons/pos_restaurant/static/src/js/models.js b/addons/pos_restaurant/static/src/js/models.js index bcf315ad600..f9126bc33b8 100644 --- a/addons/pos_restaurant/static/src/js/models.js +++ b/addons/pos_restaurant/static/src/js/models.js @@ -45,7 +45,7 @@ patch(PosGlobalState.prototype, "pos_restaurant.PosGlobalState", { } }, isInterfacePrinter() { - return this.env.pos.config.iface_printers; + return this.config.iface_printers; }, addSubmitOrderButton() { return this.config.module_pos_restaurant && this.unwatched.printers.length; diff --git a/addons/web/static/src/core/l10n/localization_service.js b/addons/web/static/src/core/l10n/localization_service.js index 2188cfb05f0..553edf9b4a0 100644 --- a/addons/web/static/src/core/l10n/localization_service.js +++ b/addons/web/static/src/core/l10n/localization_service.js @@ -87,6 +87,7 @@ export const localizationService = { thousandsSep: userLocalization.thousands_sep, weekStart: userLocalization.week_start, }); + return localization; }, }; diff --git a/addons/web/static/tests/helpers/mock_services.js b/addons/web/static/tests/helpers/mock_services.js index 2e147246037..dc2e2778662 100644 --- a/addons/web/static/tests/helpers/mock_services.js +++ b/addons/web/static/tests/helpers/mock_services.js @@ -43,6 +43,7 @@ export function makeFakeLocalizationService(config = {}) { name: "localization", start: async (env) => { env._t = _t; + return localization; }, }; }