From 6a7459c1c01014d5df437da7af9cdfb68a4d63e5 Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Tue, 3 Jan 2023 13:11:09 +0000 Subject: [PATCH] [REF] point_of_sale, *: refactor error handling Previously, the error handling was done inside of Chrome. This is unecessary as none of the error handling code needs access to the Chrome instance. Instead, this commit moves the error handling to standard error handlers. This commit also removes a bunch of ad-hoc error handling code that was doing try/catch around some RPCs, only to catch the error that happens when the client is offline. In a lot of those cases the code would display any other type of error as an unknown error instead of letting the error service do its job and in almost all cases the only difference with the default handler is that the offline message was slightly customized to tell the user more precisely which operation failed, which doesn't seem necessary. Part-of: odoo/odoo#108891 --- .../l10n_fr_pos_cert/static/src/js/Chrome.js | 29 +--- .../src/app/error_handlers/error_handlers.js | 61 +++++++ .../static/src/app/sound/sound_service.js | 7 +- addons/point_of_sale/static/src/js/Chrome.js | 124 +++----------- .../src/js/ChromeWidgets/HeaderButton.js | 22 +-- .../static/src/js/Popups/ClosePosPopup.js | 20 ++- .../PartnerListScreen/PartnerListScreen.js | 28 +--- .../js/Screens/PaymentScreen/PaymentScreen.js | 11 +- .../ControlButtons/ProductInfoButton.js | 22 +-- .../js/Screens/ProductScreen/ProductScreen.js | 26 +-- .../ProductsWidgetControlPanel.js | 61 +++---- .../ControlButtons/InvoiceButton.js | 10 -- addons/point_of_sale/static/src/js/utils.js | 28 ---- .../src/js/ChromeWidgets/TicketButton.js | 15 -- .../src/js/Screens/FloorScreen/FloorScreen.js | 151 ++++++------------ .../OrderManagementScreen/SaleOrderFetcher.js | 23 +-- .../static/src/js/SetSaleOrderButton.js | 44 ++--- .../static/src/js/models.js | 29 +--- 18 files changed, 205 insertions(+), 506 deletions(-) create mode 100644 addons/point_of_sale/static/src/app/error_handlers/error_handlers.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 03bdafa0eff..2221fc96b8a 100644 --- a/addons/l10n_fr_pos_cert/static/src/js/Chrome.js +++ b/addons/l10n_fr_pos_cert/static/src/js/Chrome.js @@ -2,8 +2,6 @@ import Chrome from "@point_of_sale/js/Chrome"; import Registries from "@point_of_sale/js/Registries"; -import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service"; -import { identifyError } from "@point_of_sale/js/utils"; const PosFrCertChrome = (Chrome) => class extends Chrome { @@ -13,30 +11,9 @@ const PosFrCertChrome = (Chrome) => const now = Date.now(); const limitDate = new Date(this.env.pos.pos_session.start_at); limitDate.setDate(limitDate.getDate() + 1); - if (limitDate < now) { - try { - const info = await this.env.pos.getClosePosInfo(); - this.showPopup("ClosePosPopup", { info: info }); - } catch (e) { - if ( - identifyError(e) instanceof ConnectionLostError || - ConnectionAbortedError - ) { - this.showPopup("OfflineErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t( - "Please check your internet connection and try again." - ), - }); - } else { - this.showPopup("ErrorPopup", { - title: this.env._t("Unknown Error"), - body: this.env._t( - "An unknown error prevents us from getting closing information." - ), - }); - } - } + if (limitDate.getTime() < now) { + const info = await this.env.pos.getClosePosInfo(); + this.showPopup("ClosePosPopup", { info: info }); } } } 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 new file mode 100644 index 00000000000..7b7ffe081d0 --- /dev/null +++ b/addons/point_of_sale/static/src/app/error_handlers/error_handlers.js @@ -0,0 +1,61 @@ +/** @odoo-module */ + +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 } from "@point_of_sale/js/Gui"; + +export function identifyError(error) { + return error && error.legacy ? error.message : error; +} + +function rpcErrorHandler(env, error, originalError) { + error = identifyError(originalError); + if (error instanceof RPCError) { + const { message, data } = error; + if (odooExceptionTitleMap.has(error.exceptionName)) { + const title = odooExceptionTitleMap.get(error.exceptionName).toString(); + Gui.showPopup("ErrorPopup", { title, body: data.message }); + } else { + Gui.showPopup("ErrorTracebackPopup", { + title: message, + body: data.message + "\n" + data.debug + "\n", + }); + } + return true; + } +} +registry.category("error_handlers").add("rpcErrorHandler", rpcErrorHandler); + +function offlineErrorHandler(env, error, originalError) { + error = identifyError(originalError); + if (error instanceof ConnectionLostError) { + Gui.showPopup("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." + ), + }); + return true; + } +} +registry.category("error_handlers").add("offlineErrorHandler", offlineErrorHandler); + +function defaultErrorHandler(env, error, originalError) { + error = identifyError(originalError); + if (error instanceof Error) { + Gui.showPopup("ErrorTracebackPopup", { + title: `${error.name}: ${error.message}`, + body: error.traceback, + }); + } else { + Gui.showPopup("ErrorPopup", { + title: env._t("Unknown Error"), + body: env._t("Unable to show information about this error."), + }); + } + return true; +} +registry + .category("error_handlers") + .add("defaultErrorHandler", defaultErrorHandler, { sequence: 99 }); diff --git a/addons/point_of_sale/static/src/app/sound/sound_service.js b/addons/point_of_sale/static/src/app/sound/sound_service.js index cdcae67b93a..2feb82d25c0 100644 --- a/addons/point_of_sale/static/src/app/sound/sound_service.js +++ b/addons/point_of_sale/static/src/app/sound/sound_service.js @@ -10,16 +10,11 @@ registry class SoundContainer extends Component { static template = xml` - `; static props = { sounds: Object, }; - onError(sound) { - sound.cleanup(); - const err = new Error(`Couldn't load sound at "${sound.src}"`); - throw err; - } } export const soundService = { diff --git a/addons/point_of_sale/static/src/js/Chrome.js b/addons/point_of_sale/static/src/js/Chrome.js index c9646ff9f42..28df66f11fe 100644 --- a/addons/point_of_sale/static/src/js/Chrome.js +++ b/addons/point_of_sale/static/src/js/Chrome.js @@ -7,13 +7,7 @@ import PosComponent from "@point_of_sale/js/PosComponent"; import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer"; import Registries from "@point_of_sale/js/Registries"; import IndependentToOrderScreen from "@point_of_sale/js/Misc/IndependentToOrderScreen"; -import { identifyError, batched } from "@point_of_sale/js/utils"; -import { odooExceptionTitleMap } from "@web/core/errors/error_dialogs"; -import { - ConnectionLostError, - ConnectionAbortedError, - RPCError, -} from "@web/core/network/rpc_service"; +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"; @@ -164,17 +158,11 @@ export class Chrome extends PosComponent { * This will load pos and assign it to the environment. */ async start() { - registry.category("error_handlers").add( - "posErrorHandler", - (env, ...noEnvArgs) => { - return this.errorHandler(this.env, ...noEnvArgs); - }, - { sequence: 0 } - ); // 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"); - configureGui({ component: this }); // FIXME POSREF + configureGui({ component: this }); // FIXME POSREF: move Gui functions to services try { await this.env.pos.load_server_data(); @@ -198,8 +186,8 @@ export class Chrome extends PosComponent { this._showStartScreen(); setTimeout(() => this._runBackgroundTasks()); } catch (error) { - let title = "Unknown Error", - body; + let title = "Unknown Error"; + let body; if (error.message && [100, 200, 404, -32098].includes(error.message.code)) { // this is the signature of rpc error @@ -219,11 +207,7 @@ export class Chrome extends PosComponent { body = error.stack; } - await this.showPopup("ErrorTracebackPopup", { - title, - body, - exitButtonIsShown: true, - }); + return this.showPopup("ErrorTracebackPopup", { title, body, exitButtonIsShown: true }); } registry.category("main_components").add("BlockUI", BlockUiFromRegistry); @@ -271,7 +255,7 @@ export class Chrome extends PosComponent { this.env.proxy .autoconnect({ force_ip: this.env.pos.config.proxy_ip || undefined, - progress: function (prog) { }, + progress: function (prog) {}, }) .then( () => { @@ -383,24 +367,25 @@ export class Chrome extends PosComponent { console.warn(error); const reason = this.env.pos.failed ? this.env._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." - ) + "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." + ) : this.env._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." - ); + "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.showPopup("ConfirmPopup", { title: this.env._t("Offline Orders"), body: reason, }); if (confirmed) { + // FIXME POSREF setting the location prevents the next render, the loading screen never shows this.state.uiState = "CLOSING"; this.state.loadingSkipButtonIsShown = false; window.location = "/web#action=point_of_sale.action_client_pos_menu"; @@ -497,75 +482,6 @@ export class Chrome extends PosComponent { get showCashMoveButton() { return this.env.pos && this.env.pos.config && this.env.pos.config.cash_control; } - - // UNEXPECTED ERROR HANDLING // - - /** - * This method is used to handle unexpected errors. It is registered to - * the `error_handlers` service when this component is properly mounted. - * See `onMounted` hook of the `ChromeAdapter` component. - * @param {*} env - * @param {UncaughtClientError | UncaughtPromiseError} error - * @param {*} originalError - * @returns {boolean} - */ - errorHandler(env, error, originalError) { - if (!env.pos) { - return false; - } - const errorToHandle = identifyError(originalError); - // Assume that the unhandled falsey rejections can be ignored. - if (errorToHandle) { - this._errorHandler(error, errorToHandle); - } - return true; - } - - _errorHandler(error, errorToHandle) { - if (errorToHandle instanceof RPCError) { - const { message, data } = errorToHandle; - if (odooExceptionTitleMap.has(errorToHandle.exceptionName)) { - const title = odooExceptionTitleMap.get(errorToHandle.exceptionName).toString(); - this.showPopup("ErrorPopup", { title, body: data.message }); - } else { - this.showPopup("ErrorTracebackPopup", { - title: message, - body: data.message + "\n" + data.debug + "\n", - }); - } - } else if (errorToHandle instanceof ConnectionLostError) { - this.showPopup("OfflineErrorPopup", { - title: this.env._t("Connection is lost"), - body: this.env._t("Check the internet connection then try again."), - }); - } else if (errorToHandle instanceof ConnectionAbortedError) { - this.showPopup("OfflineErrorPopup", { - title: this.env._t("Connection is aborted"), - body: this.env._t("Check the internet connection then try again."), - }); - } else if (errorToHandle instanceof Error) { - // If `errorToHandle` is a normal Error (such as TypeError), - // the annotated traceback can be found from `error`. - this.showPopup("ErrorTracebackPopup", { - // Hopefully the message is translated. - title: `${errorToHandle.name}: ${errorToHandle.message}`, - body: error.traceback, - }); - } else { - // Hey developer. It's your fault that the error reach here. - // Please, throw an Error object in order to get stack trace of the error. - // At least we can find the file that throws the error when you look - // at the console. - this.showPopup("ErrorPopup", { - title: this.env._t("Unknown Error"), - body: this.env._t("Unable to show information about this error."), - }); - console.error( - "Unknown error. Unable to show information about this error.", - errorToHandle - ); - } - } } Chrome.template = "Chrome"; Object.defineProperty(Chrome, "components", { 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 b94b7589e19..ff38bf77b1d 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js @@ -2,31 +2,13 @@ import PosComponent from "@point_of_sale/js/PosComponent"; import Registries from "@point_of_sale/js/Registries"; -import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service"; -import { identifyError } from "@point_of_sale/js/utils"; // Previously HeaderButtonWidget // This is the close session button class HeaderButton extends PosComponent { async onClick() { - try { - const info = await this.env.pos.getClosePosInfo(); - this.showPopup("ClosePosPopup", { info: info, keepBehind: true }); - } catch (e) { - if (identifyError(e) instanceof ConnectionAbortedError || ConnectionLostError) { - this.showPopup("OfflineErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t("Please check your internet connection and try again."), - }); - } else { - this.showPopup("ErrorPopup", { - title: this.env._t("Unknown Error"), - body: this.env._t( - "An unknown error prevents us from getting closing information." - ), - }); - } - } + const info = await this.env.pos.getClosePosInfo(); + this.showPopup("ClosePosPopup", { info: info, keepBehind: true }); } } HeaderButton.template = "HeaderButton"; 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 f0e9e3fe780..befc433b188 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -2,8 +2,8 @@ import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import Registries from "@point_of_sale/js/Registries"; -import { identifyError } from "@point_of_sale/js/utils"; -import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service"; +import { identifyError } from "@point_of_sale/app/error_handlers/error_handlers"; +import { ConnectionLostError } from "@web/core/network/rpc_service"; const { useState } = owl; class ClosePosPopup extends AbstractAwaitablePopup { @@ -152,16 +152,14 @@ class ClosePosPopup extends AbstractAwaitablePopup { } window.location = "/web#action=point_of_sale.action_client_pos_menu"; } catch (error) { - const iError = identifyError(error); - if ( - iError instanceof ConnectionLostError || - iError instanceof ConnectionAbortedError - ) { - await this.showPopup("ErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t("Cannot close the session when offline."), - }); + if (identifyError(error) instanceof ConnectionLostError) { + // Cannot redirect to backend when offline, let error handlers show the offline popup + // FIXME POSREF: doing this means closing again when online will redo the beginning of the method + // although it's impossible to close again because this.closeSessionClicked isn't reset to false + // The application state is corrupted. + throw error; } else { + // FIXME POSREF: why are we catching errors here but not anywhere else in this method? await this.showPopup("ErrorPopup", { title: this.env._t("Closing session error"), body: this.env._t( 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 fc4a87ac6f2..2aa163a961e 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 @@ -2,7 +2,6 @@ import PosComponent from "@point_of_sale/js/PosComponent"; import Registries from "@point_of_sale/js/Registries"; -import { isConnectionError } from "@point_of_sale/js/utils"; import { debounce } from "@web/core/utils/timing"; import { useListener } from "@web/core/utils/hooks"; @@ -162,25 +161,14 @@ class PartnerListScreen extends PosComponent { this.activateEditMode(); } async saveChanges(event) { - try { - const partnerId = await this.rpc({ - model: "res.partner", - method: "create_from_ui", - args: [event.detail.processedChanges], - }); - await this.env.pos.load_new_partners(); - this.state.selectedPartner = this.env.pos.db.get_partner_by_id(partnerId); - this.confirm(); - } catch (error) { - if (isConnectionError(error)) { - await this.showPopup("OfflineErrorPopup", { - title: this.env._t("Offline"), - body: this.env._t("Unable to save changes."), - }); - } else { - throw error; - } - } + const partnerId = await this.rpc({ + model: "res.partner", + method: "create_from_ui", + args: [event.detail.processedChanges], + }); + await this.env.pos.load_new_partners(); + this.state.selectedPartner = this.env.pos.db.get_partner_by_id(partnerId); + this.confirm(); } async searchPartner() { const result = await this.getNewPartners(); 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 01e28665cb9..55115092265 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 @@ -6,7 +6,6 @@ import { useErrorHandlers } from "@point_of_sale/js/custom_hooks"; import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer"; import { useListener } from "@web/core/utils/hooks"; import Registries from "@point_of_sale/js/Registries"; -import { isConnectionError } from "@point_of_sale/js/utils"; import utils from "web.utils"; class PaymentScreen extends PosComponent { @@ -257,15 +256,7 @@ class PaymentScreen extends PosComponent { // introduce to handle invoicing error logic. await this._handlePushOrderError(error); } else { - // We don't block for connection error. But we rethrow for any other errors. - if (isConnectionError(error)) { - this.showPopup("OfflineErrorPopup", { - title: this.env._t("Connection Error"), - body: this.env._t("Order is not synced. Check your internet connection"), - }); - } else { - throw error; - } + throw error; } } finally { // Always show the next screen regardless of error since pos has to 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 38cafb7ada2..4c8440ca6fc 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 @@ -4,8 +4,6 @@ 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 Registries from "@point_of_sale/js/Registries"; -import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service"; -import { identifyError } from "@point_of_sale/js/utils"; class ProductInfoButton extends PosComponent { setup() { @@ -17,24 +15,8 @@ class ProductInfoButton extends PosComponent { if (orderline) { const product = orderline.get_product(); const quantity = orderline.get_quantity(); - try { - const info = await this.env.pos.getProductInfo(product, quantity); - this.showPopup("ProductInfoPopup", { info: info, product: product }); - } catch (e) { - if (identifyError(e) instanceof ConnectionLostError || ConnectionAbortedError) { - this.showPopup("OfflineErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t("Cannot access product information screen if offline."), - }); - } else { - this.showPopup("ErrorPopup", { - title: this.env._t("Unknown error"), - body: this.env._t( - "An unknown error prevents us from loading product information." - ), - }); - } - } + const info = await this.env.pos.getProductInfo(product, quantity); + this.showPopup("ProductInfoPopup", { info: info, product: product }); } } } 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 57fab179bb0..d9f1ebe879b 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 @@ -6,7 +6,6 @@ import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer"; import { useListener } from "@web/core/utils/hooks"; import Registries from "@point_of_sale/js/Registries"; import { useBarcodeReader } from "@point_of_sale/js/custom_hooks"; -import { isConnectionError } from "@point_of_sale/js/utils"; import { parse } from "web.field_utils"; const { onMounted, useState } = owl; @@ -233,25 +232,12 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) { if (!product) { // find the barcode in the backend let foundProductIds = []; - try { - foundProductIds = await this.rpc({ - model: "product.product", - method: "search", - args: [[["barcode", "=", code.base_code]]], - context: this.env.session.user_context, - }); - } catch (error) { - if (isConnectionError(error)) { - return this.showPopup("OfflineErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t( - "Product is not loaded. Tried loading the product from the server but there is a network error." - ), - }); - } else { - throw error; - } - } + foundProductIds = await this.rpc({ + model: "product.product", + method: "search", + args: [[["barcode", "=", code.base_code]]], + context: this.env.session.user_context, + }); if (foundProductIds.length) { await this.env.pos._addProducts(foundProductIds); // assume that the result is unique. 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 b8252852c0b..f51b26ad56a 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 @@ -1,7 +1,5 @@ /** @odoo-module */ -import { identifyError } from "@point_of_sale/js/utils"; -import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service"; import PosComponent from "@point_of_sale/js/PosComponent"; import Registries from "@point_of_sale/js/Registries"; import { debounce } from "@web/core/utils/timing"; @@ -75,47 +73,30 @@ class ProductsWidgetControlPanel extends PosComponent { return; } - try { - const ProductIds = await this.rpc({ - model: "product.product", - method: "search", - args: [ - [ - "&", - ["available_in_pos", "=", true], - "|", - "|", - ["name", "ilike", this.state.searchInput], - ["default_code", "ilike", this.state.searchInput], - ["barcode", "ilike", this.state.searchInput], - ], + const ProductIds = await this.rpc({ + model: "product.product", + method: "search", + args: [ + [ + "&", + ["available_in_pos", "=", true], + "|", + "|", + ["name", "ilike", this.state.searchInput], + ["default_code", "ilike", this.state.searchInput], + ["barcode", "ilike", this.state.searchInput], ], - context: this.env.session.user_context, - }); - if (ProductIds.length) { - if (!this.env.pos.isEveryProductLoaded) { - await this.env.pos.updateIsEveryProductLoaded(); - } - await this.env.pos._addProducts(ProductIds, false); - } - this.trigger("update-product-list"); - return ProductIds; - } catch (error) { - const identifiedError = identifyError(error); - if ( - identifiedError instanceof ConnectionLostError || - identifiedError instanceof ConnectionAbortedError - ) { - return this.showPopup("OfflineErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t( - "Product is not loaded. Tried loading the product from the server but there is a network error." - ), - }); - } else { - throw error; + ], + context: this.env.session.user_context, + }); + if (ProductIds.length) { + if (!this.env.pos.isEveryProductLoaded) { + await this.env.pos.updateIsEveryProductLoaded(); } + await this.env.pos._addProducts(ProductIds, false); } + this.trigger("update-product-list"); + return ProductIds; } } ProductsWidgetControlPanel.template = "ProductsWidgetControlPanel"; 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 fd6c8005e6b..0fa138f85a0 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,7 +1,6 @@ /** @odoo-module */ import { useListener } from "@web/core/utils/hooks"; -import { isConnectionError } from "@point_of_sale/js/utils"; import PosComponent from "@point_of_sale/js/PosComponent"; import Registries from "@point_of_sale/js/Registries"; @@ -111,15 +110,6 @@ class InvoiceButton extends PosComponent { try { this.el.style.pointerEvents = "none"; await this._invoiceOrder(); - } catch (error) { - if (isConnectionError(error)) { - this.showPopup("ErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t("Unable to invoice order."), - }); - } else { - throw error; - } } finally { this.el.style.pointerEvents = "auto"; } diff --git a/addons/point_of_sale/static/src/js/utils.js b/addons/point_of_sale/static/src/js/utils.js index afefdc5e6bb..1d5cb197758 100644 --- a/addons/point_of_sale/static/src/js/utils.js +++ b/addons/point_of_sale/static/src/js/utils.js @@ -1,6 +1,4 @@ /** @odoo-module */ -import { ConnectionAbortedError, ConnectionLostError } from "@web/core/network/rpc_service"; - export function getFileAsText(file) { return new Promise((resolve, reject) => { if (!file) { @@ -35,32 +33,6 @@ export const nextFrame = () => { }); }; -export function isConnectionError(error) { - const _error = identifyError(error); - return _error instanceof ConnectionAbortedError || _error instanceof ConnectionLostError; -} - -export function identifyError(error) { - if (!error) { - return error; - } - let errorToHandle; - if (error.legacy) { - // error.message is either RPCError or ConnectionLostError - errorToHandle = error.message; - } else if (error.event && error.event.type == "abort") { - // Check if there is event and if the event type is abort. - // If so, then it's supposed to be a ConnectionAbortedError, - // however, it was stripped in the patch of rpc in `mapLegacyEnvToWowlEnv`. - // We recreate the error object here so that in the actual handler, - // ConnectionAbortedError and ConnectionLostError are handled properly. - errorToHandle = new ConnectionAbortedError(error.message); - } else if (error instanceof Error) { - errorToHandle = error; - } - return errorToHandle || error; -} - /** * Creates a batched version of a callback so that all calls to it in the same * microtick will only call the original callback once. diff --git a/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js b/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js index 025acf4e915..3a0d6df6dad 100644 --- a/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js +++ b/addons/pos_restaurant/static/src/js/ChromeWidgets/TicketButton.js @@ -2,7 +2,6 @@ import TicketButton from "@point_of_sale/js/ChromeWidgets/TicketButton"; import Registries from "@point_of_sale/js/Registries"; -import { isConnectionError } from "@point_of_sale/js/utils"; const PosResTicketButton = (TicketButton) => class extends TicketButton { @@ -15,20 +14,6 @@ const PosResTicketButton = (TicketButton) => try { this.env.pos.setLoadingOrderState(true); await this.env.pos._syncAllOrdersFromServer(); - } catch (error) { - if (isConnectionError(error)) { - await this.showPopup("OfflineErrorPopup", { - title: this.env._t("Offline"), - body: this.env._t( - "Due to a connection error, the orders are not synchronized." - ), - }); - } else { - this.showPopup("ErrorPopup", { - title: this.env._t("Unknown error"), - body: error.message, - }); - } } finally { this.env.pos.setLoadingOrderState(false); this.showScreen("TicketScreen"); 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 c45c6d086ca..2ff1a6d2c0d 100644 --- a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js @@ -3,7 +3,6 @@ import PosComponent from "@point_of_sale/js/PosComponent"; import Registries from "@point_of_sale/js/Registries"; import { debounce } from "@web/core/utils/timing"; -import { isConnectionError } from "@point_of_sale/js/utils"; const { onPatched, onMounted, onWillUnmount, useRef, useState } = owl; @@ -87,21 +86,9 @@ class FloorScreen extends PosComponent { delete newTable.id; newTable.floor_id = [this.activeFloor.id, ""]; newTable.floor = this.activeFloor; - try { - await this._save(newTable); - this.activeTables.push(newTable); - return newTable; - } catch (error) { - if (isConnectionError(error)) { - await this.showPopup("ErrorPopup", { - title: this.env._t("Offline"), - body: this.env._t("Unable to create table because you are offline."), - }); - return; - } else { - throw error; - } - } + await this._save(newTable); + this.activeTables.push(newTable); + return newTable; } _getNewTableName(name) { if (name) { @@ -132,33 +119,22 @@ class FloorScreen extends PosComponent { if (this.state.isEditMode) { return; } - try { - const result = await this.rpc({ - model: "pos.config", - method: "get_tables_order_count", - args: [this.env.pos.config.id], - }); - result.forEach((table) => { - const table_obj = this.env.pos.tables_by_id[table.id]; - const unsynced_orders = this.env.pos.getTableOrders(table_obj.id).filter( - (o) => - o.server_id === undefined && - (o.orderlines.length !== 0 || o.paymentlines.length !== 0) && - // do not count the orders that are already finalized - !o.finalized - ).length; - table_obj.order_count = table.orders + unsynced_orders; - }); - } catch (error) { - if (isConnectionError(error)) { - await this.showPopup("OfflineErrorPopup", { - title: this.env._t("Offline"), - body: this.env._t("Unable to get orders count"), - }); - } else { - throw error; - } - } + const result = await this.rpc({ + model: "pos.config", + method: "get_tables_order_count", + args: [this.env.pos.config.id], + }); + result.forEach((table) => { + const table_obj = this.env.pos.tables_by_id[table.id]; + const unsynced_orders = this.env.pos.getTableOrders(table_obj.id).filter( + (o) => + o.server_id === undefined && + (o.orderlines.length !== 0 || o.paymentlines.length !== 0) && + // do not count the orders that are already finalized + !o.finalized + ).length; + table_obj.order_count = table.orders + unsynced_orders; + }); } get activeFloor() { return this.env.pos.floors_by_id[this.state.selectedFloorId]; @@ -211,21 +187,10 @@ class FloorScreen extends PosComponent { if (this.state.isEditMode) { this.state.selectedTableId = table.id; } else { - try { - if (this.env.pos.orderToTransfer) { - await this.env.pos.transferTable(table); - } else { - await this.env.pos.setTable(table); - } - } catch (error) { - if (isConnectionError(error)) { - await this.showPopup("OfflineErrorPopup", { - title: this.env._t("Offline"), - body: this.env._t("Unable to fetch orders"), - }); - } else { - throw error; - } + if (this.env.pos.orderToTransfer) { + await this.env.pos.transferTable(table); + } else { + await this.env.pos.setTable(table); } const order = this.env.pos.get_order(); this.showScreen(order.get_screen_data().name); @@ -302,22 +267,11 @@ class FloorScreen extends PosComponent { async setFloorColor(color) { this.state.floorBackground = color; this.activeFloor.background_color = color; - try { - await this.rpc({ - model: "restaurant.floor", - method: "write", - args: [[this.activeFloor.id], { background_color: color }], - }); - } catch (error) { - if (isConnectionError(error)) { - await this.showPopup("OfflineErrorPopup", { - title: this.env._t("Offline"), - body: this.env._t("Unable to change background color"), - }); - } else { - throw error; - } - } + await this.rpc({ + model: "restaurant.floor", + method: "write", + args: [[this.activeFloor.id], { background_color: color }], + }); } async deleteTable() { if (!this.selectedTable) { @@ -330,37 +284,26 @@ class FloorScreen extends PosComponent { if (!confirmed) { return; } - try { - const originalSelectedTableId = this.state.selectedTableId; - await this.rpc({ - model: "restaurant.table", - method: "create_from_ui", - args: [{ active: false, id: originalSelectedTableId }], - }); - this.activeFloor.tables = this.activeTables.filter( - (table) => table.id !== originalSelectedTableId - ); - // Value of an object can change inside async function call. - // Which means that in this code block, the value of `state.selectedTableId` - // before the await call can be different after the finishing the await call. - // Since we wanted to disable the selected table after deletion, we should be - // setting the selectedTableId to null. However, we only do this if nothing - // else is selected during the rpc call. - if (this.state.selectedTableId === originalSelectedTableId) { - this.state.selectedTableId = null; - } - delete this.env.pos.tables_by_id[originalSelectedTableId]; - this.env.pos.TICKET_SCREEN_STATE.syncedOrders.cache = {}; - } catch (error) { - if (isConnectionError(error)) { - await this.showPopup("OfflineErrorPopup", { - title: this.env._t("Offline"), - body: this.env._t("Unable to delete table"), - }); - } else { - throw error; - } + const originalSelectedTableId = this.state.selectedTableId; + await this.rpc({ + model: "restaurant.table", + method: "create_from_ui", + args: [{ active: false, id: originalSelectedTableId }], + }); + this.activeFloor.tables = this.activeTables.filter( + (table) => table.id !== originalSelectedTableId + ); + // Value of an object can change inside async function call. + // Which means that in this code block, the value of `state.selectedTableId` + // before the await call can be different after the finishing the await call. + // Since we wanted to disable the selected table after deletion, we should be + // setting the selectedTableId to null. However, we only do this if nothing + // else is selected during the rpc call. + if (this.state.selectedTableId === originalSelectedTableId) { + this.state.selectedTableId = null; } + delete this.env.pos.tables_by_id[originalSelectedTableId]; + this.env.pos.TICKET_SCREEN_STATE.syncedOrders.cache = {}; } } FloorScreen.template = "FloorScreen"; diff --git a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js index 67fc3c55d43..3a9351b0c57 100644 --- a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js +++ b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderFetcher.js @@ -1,7 +1,6 @@ /** @odoo-module */ import { Gui } from "@point_of_sale/js/Gui"; -import { isConnectionError } from "@point_of_sale/js/utils"; const { EventBus } = owl; @@ -38,24 +37,12 @@ class SaleOrderFetcher extends EventBus { * to the active orders that fits the current page. */ async fetch() { - try { - // Show orders from the backend. - const offset = this.nPerPage + (this.currentPage - 1 - 1) * this.nPerPage; - const limit = this.nPerPage; - this.ordersToShow = await this._fetch(limit, offset); + // Show orders from the backend. + const offset = this.nPerPage + (this.currentPage - 1 - 1) * this.nPerPage; + const limit = this.nPerPage; + this.ordersToShow = await this._fetch(limit, offset); - this.trigger("update"); - } catch (error) { - if (isConnectionError(error)) { - Gui.showPopup("ErrorPopup", { - title: this.comp.env._t("Network Error"), - body: this.comp.env._t("Unable to fetch orders if offline."), - }); - Gui.setSyncStatus("error"); - } else { - throw error; - } - } + this.trigger("update"); } /** * This returns the orders from the backend that needs to be shown. diff --git a/addons/pos_sale/static/src/js/SetSaleOrderButton.js b/addons/pos_sale/static/src/js/SetSaleOrderButton.js index 6c17d1abb49..bac969024af 100644 --- a/addons/pos_sale/static/src/js/SetSaleOrderButton.js +++ b/addons/pos_sale/static/src/js/SetSaleOrderButton.js @@ -4,7 +4,6 @@ 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 Registries from "@point_of_sale/js/Registries"; -import { isConnectionError } from "@point_of_sale/js/utils"; import { Gui } from "@point_of_sale/js/Gui"; class SetSaleOrderButton extends PosComponent { @@ -16,33 +15,22 @@ class SetSaleOrderButton extends PosComponent { return this.env.pos.get_order(); } async onClick() { - try { - // ping the server, if no error, show the screen - // Use rpc from services which resolves even when this - // component is destroyed (removed together with the popup). - await this.env.services.rpc({ - model: "sale.order", - method: "browse", - 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); - } catch (error) { - if (isConnectionError(error)) { - this.showPopup("ErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t("Cannot access order management screen if offline."), - }); - } else { - throw error; - } - } + // ping the server, if no error, show the screen + // Use rpc from services which resolves even when this + // component is destroyed (removed together with the popup). + await this.env.services.rpc({ + model: "sale.order", + method: "browse", + 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); } } SetSaleOrderButton.template = "SetSaleOrderButton"; 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 757a58c73a0..f1ba2094bf9 100644 --- a/addons/pos_sale_product_configurator/static/src/js/models.js +++ b/addons/pos_sale_product_configurator/static/src/js/models.js @@ -3,8 +3,6 @@ import { Gui } from "@point_of_sale/js/Gui"; import { Order } from "@point_of_sale/js/models"; import Registries from "@point_of_sale/js/Registries"; -import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service"; -import { identifyError } from "@point_of_sale/js/utils"; const PosSaleProductConfiguratorOrder = (Order) => class PosSaleProductConfiguratorOrder extends Order { @@ -20,30 +18,9 @@ const PosSaleProductConfiguratorOrder = (Order) => args: [[product.id]], }); if (isProductLoaded) { - try { - const quantity = this.get_selected_orderline().get_quantity(); - const info = await this.pos.getProductInfo(product, quantity); - Gui.showPopup("ProductInfoPopup", { info: info, product: product }); - } catch (e) { - if ( - identifyError(e) instanceof ConnectionLostError || - ConnectionAbortedError - ) { - Gui.showPopup("OfflineErrorPopup", { - title: this.env._t("Network Error"), - body: this.env._t( - "Cannot access product information screen if offline." - ), - }); - } else { - Gui.showPopup("ErrorPopup", { - title: this.env._t("Unknown error"), - body: this.env._t( - "An unknown error prevents us from loading product information." - ), - }); - } - } + const quantity = this.get_selected_orderline().get_quantity(); + const info = await this.pos.getProductInfo(product, quantity); + Gui.showPopup("ProductInfoPopup", { info: info, product: product }); } } }