[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
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
@@ -10,16 +10,11 @@ registry
|
||||
|
||||
class SoundContainer extends Component {
|
||||
static template = xml`<t t-foreach="props.sounds" t-as="sound" t-key="sound">
|
||||
<audio autoplay="true" t-att-src="sound_value.src" t-on-ended="sound_value.cleanup" t-on-error="() => onError(sound_value)"/>
|
||||
<audio autoplay="true" t-att-src="sound_value.src" t-on-ended="sound_value.cleanup" t-on-error="sound_value.cleanup"/>
|
||||
</t>`;
|
||||
static props = {
|
||||
sounds: Object,
|
||||
};
|
||||
onError(sound) {
|
||||
sound.cleanup();
|
||||
const err = new Error(`Couldn't load sound at "${sound.src}"`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export const soundService = {
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-20
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
+21
-40
@@ -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";
|
||||
|
||||
-10
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user