[REF] pos*: move show(Temp)Screen, make number_buffer and popup services

*: l10n_fr_pos_cert, point_of_sale, pos_discount, pos_hr,
pos_hr_restaurant, pos_loyalty, pos_mercury, pos_restaurant, pos_sale,
pos_sale_product_configurator, pos_six, pos_stripe

Continuing to move things out of the Chrome god component, this commit
does the following things:

 - moves the showScreen and showTempScreen methods and the corresponding
closing methods into the pos store so that components that need them can
access them through the pos service in a more explicit way. To that end,
these methods have been removed from PosComponent and Gui.

 - introduces a popup service instead of using a bus in the env, this is
mechanically very similar but closer to what's done in web and hopefully
a first step to using the dialog service instead. It also removes the
showPopup method from the PosComponent and Gui, to make components
depending on the popup service explicit.

 - makes the number buffer into a service instead of being a directly
imported singleton, which will enable it to be used with hooks instead
at a later point, and allows easier dependency injection in tests.

 - removes showNotification from the PosComponent and Gui, the
notification system was already converted to a service in a previous
commit for the same reasons as showPopup and the end goal is to use the
notification service from web to reduce duplication.

closes odoo/odoo#111844

Related: odoo/enterprise#36668
Signed-off-by: Samuel Degueldre <sad@odoo.com>
This commit is contained in:
Samuel Degueldre
2023-02-07 16:38:34 +01:00
parent 1c7c29eeab
commit 0d537e58a9
104 changed files with 983 additions and 1003 deletions
@@ -13,7 +13,7 @@ patch(Chrome.prototype, "l10n_fr_pos_cert.Chrome", {
limitDate.setDate(limitDate.getDate() + 1);
if (limitDate.getTime() < now) {
const info = await this.env.pos.getClosePosInfo();
this.showPopup(ClosePosPopup, { info: info });
this.popup.add(ClosePosPopup, { info });
}
}
},
+2 -3
View File
@@ -1,6 +1,5 @@
/** @odoo-module */
import { Gui } from "@point_of_sale/js/Gui";
import { PosGlobalState, Order, Orderline } from "@point_of_sale/js/models";
import core from "web.core";
import { patch } from "@web/core/utils/patch";
@@ -12,7 +11,7 @@ patch(PosGlobalState.prototype, "l10n_fr_pos_cert.PosGlobalState", {
is_french_country() {
var french_countries = ["FR", "MF", "MQ", "NC", "PF", "RE", "GF", "GP", "TF"];
if (!this.company.country) {
Gui.showPopup(ErrorPopup, {
this.env.services.popup.add(ErrorPopup, {
title: _t("Missing Country"),
body: _.str.sprintf(
_t("The company %s doesn't have a country set."),
@@ -60,7 +59,7 @@ patch(Order.prototype, "l10n_fr_pos_cert.Order", {
this.pos.is_french_country() &&
this.get_orderlines().length
) {
Gui.showPopup(ErrorPopup, {
this.env.services.popup.add(ErrorPopup, {
title: _t("Fiscal Data Module error"),
body: _t("Deleting of orders is not allowed."),
});
@@ -1,15 +1,14 @@
/** @odoo-module */
import { parse } from "web.field_utils";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { Transition } from "@web/core/transition";
import { Draggable } from "@point_of_sale/js/Misc/Draggable";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { OrderImportPopup } from "@point_of_sale/js/Popups/OrderImportPopup";
import { useService } from "@web/core/utils/hooks";
import { useBus, useService } from "@web/core/utils/hooks";
import { onMounted, onWillUnmount, useRef, useState } from "@odoo/owl";
import { useRef, useState } from "@odoo/owl";
export class DebugWidget extends PosComponent {
static components = { Transition, Draggable };
@@ -17,6 +16,9 @@ export class DebugWidget extends PosComponent {
setup() {
super.setup();
this.debug = useService("debug");
this.popup = useService("popup");
const numberBuffer = useService("number_buffer");
useBus(numberBuffer, "buffer-update", this._onBufferUpdate);
this.state = useState({
barcodeInput: "",
weightInput: "",
@@ -49,14 +51,6 @@ export class DebugWidget extends PosComponent {
}).bind(this)
);
}
onMounted(() => {
numberBuffer.on("buffer-update", this, this._onBufferUpdate);
});
onWillUnmount(() => {
numberBuffer.off("buffer-update", this, this._onBufferUpdate);
});
}
toggleWidget() {
this.state.isShown = !this.state.isShown;
@@ -82,7 +76,7 @@ export class DebugWidget extends PosComponent {
await this.env.barcode_reader.scan(ean);
}
async deleteOrders() {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Delete Paid Orders ?"),
body: this.env._t(
"This operation will permanently destroy all paid orders from the local storage. You will lose all the data. This operation cannot be undone."
@@ -94,7 +88,7 @@ export class DebugWidget extends PosComponent {
}
}
async deleteUnpaidOrders() {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Delete Unpaid Orders ?"),
body: this.env._t(
"This operation will destroy all unpaid orders in the browser. You will lose all the unsaved data and exit the point of sale. This operation cannot be undone."
@@ -148,14 +142,14 @@ export class DebugWidget extends PosComponent {
const file = event.target.files[0];
if (file) {
const report = this.env.pos.import_orders(await file.text());
await this.showPopup(OrderImportPopup, { report });
await this.popup.add(OrderImportPopup, { report });
}
}
refreshDisplay() {
this.env.proxy.message("display_refresh", {});
}
_onBufferUpdate(buffer) {
this.state.buffer = buffer;
_onBufferUpdate({ detail: value }) {
this.state.buffer = value;
}
get bufferRepr() {
return `"${this.state.buffer}"`;
@@ -3,7 +3,7 @@
import { registry } from "@web/core/registry";
import { odooExceptionTitleMap } from "@web/core/errors/error_dialogs";
import { ConnectionLostError, RPCError } from "@web/core/network/rpc_service";
import { Gui, GuiNotReadyError } from "@point_of_sale/js/Gui";
import { GuiNotReadyError } from "@point_of_sale/js/Gui";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { ErrorTracebackPopup } from "@point_of_sale/js/Popups/ErrorTracebackPopup";
import { OfflineErrorPopup } from "@point_of_sale/js/Popups/OfflineErrorPopup";
@@ -18,9 +18,9 @@ function rpcErrorHandler(env, error, originalError) {
const { message, data } = error;
if (odooExceptionTitleMap.has(error.exceptionName)) {
const title = odooExceptionTitleMap.get(error.exceptionName).toString();
Gui.showPopup(ErrorPopup, { title, body: data.message });
env.services.popup.add(ErrorPopup, { title, body: data.message });
} else {
Gui.showPopup(ErrorTracebackPopup, {
env.services.popup.add(ErrorTracebackPopup, {
title: message,
body: data.message + "\n" + data.debug + "\n",
});
@@ -33,7 +33,7 @@ registry.category("error_handlers").add("rpcErrorHandler", rpcErrorHandler);
function offlineErrorHandler(env, error, originalError) {
error = identifyError(originalError);
if (error instanceof ConnectionLostError) {
Gui.showPopup(OfflineErrorPopup, {
env.services.popup.add(OfflineErrorPopup, {
title: env._t("Couldn't connect to the server"),
body: env._t(
"The operation couldn't be completed because you are offline. Check your internet connection and try again."
@@ -45,14 +45,14 @@ function offlineErrorHandler(env, error, originalError) {
registry.category("error_handlers").add("offlineErrorHandler", offlineErrorHandler);
function defaultErrorHandler(env, error, originalError) {
error = identifyError(originalError);
originalError = identifyError(originalError);
if (error instanceof Error) {
Gui.showPopup(ErrorTracebackPopup, {
title: `${error.name}: ${error.message}`,
env.services.popup.add(ErrorTracebackPopup, {
title: `${originalError.name}: ${originalError.message}`,
body: error.traceback,
});
} else {
Gui.showPopup(ErrorPopup, {
env.services.popup.add(ErrorPopup, {
title: env._t("Unknown Error"),
body: env._t("Unable to show information about this error."),
});
@@ -1,7 +1,7 @@
/** @odoo-module */
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { usePos } from "@point_of_sale/app/pos_store";
import { usePos } from "@point_of_sale/app/pos_hook";
import { useService } from "@web/core/utils/hooks";
import { CashierName } from "@point_of_sale/js/ChromeWidgets/CashierName";
@@ -4,7 +4,7 @@ import { useListener } from "@web/core/utils/hooks";
import { parse } from "web.field_utils";
import { barcodeService } from "@barcodes/barcode_service";
import { _t } from "web.core";
import { Gui } from "@point_of_sale/js/Gui";
import { registry } from "@web/core/registry";
const { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } = owl;
const INPUT_KEYS = new Set(
@@ -60,10 +60,11 @@ const getDefaultConfig = () => ({
* - Write more integration tests. NumberPopup can be used as test component.
*/
class NumberBuffer extends EventBus {
constructor() {
constructor({ sound }) {
super();
this.isReset = false;
this.bufferHolderStack = [];
this.sound = sound;
}
/**
* @returns {String} value of the buffer, e.g. '-95.79'
@@ -294,7 +295,7 @@ class NumberBuffer extends EventBus {
if (isFirstInput) {
this.state.buffer = "" + input;
} else if (this.state.buffer.length > 12) {
Gui.playSound("bell");
this.sound.play("bell");
} else {
this.state.buffer += input;
}
@@ -317,4 +318,11 @@ class NumberBuffer extends EventBus {
}
}
export const numberBuffer = new NumberBuffer();
export const numberBuffer = {
dependencies: ["sound"],
start(env, { sound }) {
return new NumberBuffer({ sound });
},
};
registry.category("services").add("number_buffer", numberBuffer);
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="point_of_sale.PopupContainer" owl="1">
<div t-if="Object.keys(props.popups).length > 0" class="popups">
<t t-foreach="props.popups" t-as="popup" t-key="popup">
<div t-if="popup_last || popup_value.props.keepBehind" role="dialog" class="modal-dialog">
<t t-component="popup_value.component" t-props="popup_value.props" isActive="popup_last" />
</div>
</t>
</div>
</t>
</templates>
@@ -0,0 +1,62 @@
/** @odoo-module */
import { reactive } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { usePos } from "@point_of_sale/app/pos_hook";
export class PopupContainer extends PosComponent {
static template = "point_of_sale.PopupContainer";
setup() {
// FIXME POSREF: remove this after PosComponent is removed.
this.__owl__.childEnv = usePos().legacyEnv;
}
}
// FIXME POSREF: probably should use the main dialog service from web long term.
export const popupService = {
start() {
const popups = reactive({});
registry.category("main_components").add("PopupContainer", {
Component: PopupContainer,
props: { popups },
});
let popupId = 0;
return {
/**
* Displays a popup over the interface.
*
* @param {AbstractAwaitablePopup} component the popup component to
* open
* @param {Object} props props for the popup component
* @returns {Promise<any>} a Promise that fulfills when the popup
* is closed (confirmed or canceled)
*/
add(component, props) {
return new Promise((resolve) => {
const id = ++popupId;
popups[id] = {
component,
props: {
keepBehind: false,
cancelKey: "Escape",
confirmKey: "Enter",
// FIXME POSREF assigning the default props by hand defeats the point of default props
...component.defaultProps,
...props,
id,
resolve,
close(response) {
delete popups[id];
resolve(response);
},
},
};
});
},
// exposed so that overrides can use it, should not be read from outside. Maybe we should just implement closePopupsButError directly in pos instead of pos_restaurant
popups,
};
},
};
registry.category("services").add("popup", popupService);
@@ -0,0 +1,11 @@
/** @odoo-module */
import { useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
/**
* @returns {import("./pos_store").PosStore}
*/
export function usePos() {
return useState(useService("pos"));
}
@@ -4,10 +4,10 @@ import { PosGlobalState } from "@point_of_sale/js/models";
import { pos_env as legacyEnv } from "@point_of_sale/js/pos_env";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { reactive, useState, markRaw } from "@odoo/owl";
import { reactive, markRaw } from "@odoo/owl";
import { Reactive } from "@point_of_sale/utils";
export class PosStore {
export class PosStore extends Reactive {
/** @type {'LOADING' | 'READY' | 'CLOSING'} */
uiState = "LOADING";
hasBigScrollBars = false;
@@ -15,26 +15,52 @@ export class PosStore {
mainScreen = { name: null, component: null };
tempScreen = null;
legacyEnv = legacyEnv;
constructor() {
globalState = new PosGlobalState({ env: markRaw(legacyEnv) });
static serviceDependencies = ["popup"];
constructor({ popup }) {
super();
this.popup = popup;
this.setup();
}
// use setup instead of constructor because setup can be patched.
setup() {
this.globalState = new PosGlobalState({ env: markRaw(legacyEnv) });
setup() {}
showScreen(name, props) {
const component = registry.category("pos_screens").get(name);
this.mainScreen = { component, props };
// Save the screen to the order so that it is shown again when the order is selected.
if (component.storeOnOrder ?? true) {
this.globalState.get_order()?.set_screen_data({ name, props });
}
}
closeScreen() {
const { name: screenName } = this.globalState.get_order().get_screen_data();
this.showScreen(screenName);
}
// FIXME POSREF get rid of temp screens entirely?
showTempScreen(name, props = {}) {
return new Promise((resolve) => {
this.tempScreen = {
name,
component: registry.category("pos_screens").get(name),
props: { ...props, resolve },
};
});
}
closeTempScreen() {
this.tempScreen = null;
}
}
export const posService = {
start() {
return reactive(new PosStore());
dependencies: PosStore.serviceDependencies,
start(env, deps) {
return reactive(new PosStore(deps));
},
};
registry.category("services").add("pos", posService);
/**
* @returns {PosStore}
*/
export function usePos() {
return useState(useService("pos"));
}
+29 -59
View File
@@ -4,13 +4,11 @@ import { loadCSS } from "@web/core/assets";
import { useListener, useBus, useService } from "@web/core/utils/hooks";
import BarcodeParser from "barcodes.BarcodeParser";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen";
import { batched } from "@point_of_sale/js/utils";
import { debounce } from "@web/core/utils/timing";
import { Transition } from "@web/core/transition";
import { MainComponentsContainer } from "@web/core/main_components_container";
import { WithEnv } from "@web/core/utils/components";
import { WithEnv, ErrorHandler } from "@web/core/utils/components";
import { Navbar } from "@point_of_sale/app/navbar/navbar";
// ChromeAdapter imports
@@ -31,7 +29,7 @@ import {
reactive,
onWillUnmount,
} from "@odoo/owl";
import { usePos } from "@point_of_sale/app/pos_store";
import { usePos } from "@point_of_sale/app/pos_hook";
/**
* Chrome is the root component of the PoS App.
@@ -40,6 +38,7 @@ export class Chrome extends PosComponent {
static template = "Chrome"; // FIXME POSREF namespace templates
setup() {
this.pos = usePos();
this.popup = useService("popup");
// BEGIN ChromeAdapter
ProductScreen.sortControlButtons();
const legacyActionManager = useService("legacy_action_manager");
@@ -59,7 +58,14 @@ export class Chrome extends PosComponent {
window.posmodel = this.pos.globalState.debug ? reactivePos : this.pos.globalState;
this.wowlEnv = this.env;
for (const service of ["pos", "sound", "debug", "pos_notification"]) {
for (const service of [
"pos",
"sound",
"debug",
"pos_notification",
"number_buffer",
"popup",
]) {
env.services[service] = this.wowlEnv.services[service];
}
@@ -83,15 +89,12 @@ export class Chrome extends PosComponent {
super.setup();
useExternalListener(window, "beforeunload", this._onBeforeUnload);
useListener("show-main-screen", this.__showScreen);
useListener("show-temp-screen", this.__showTempScreen);
useListener("close-temp-screen", this.__closeTempScreen);
useListener("close-pos", this._closePos);
useListener("loading-skip-callback", () => this.env.proxy.stop_searching());
useListener("set-sync-status", this._onSetSyncStatus);
useListener("connect-to-proxy", this.connect_to_proxy);
useBus(this.env.posbus, "start-cash-control", this.openCashControl);
numberBuffer.activate();
useService("number_buffer").activate();
useSubEnv({
pos: reactive(
@@ -170,7 +173,8 @@ export class Chrome extends PosComponent {
? this.env.pos.config.iface_start_categ_id[0]
: 0;
this.pos.uiState = "READY";
this._showStartScreen();
const { name, props } = this.startScreen;
this.pos.showScreen(name, props);
setTimeout(() => this._runBackgroundTasks());
} catch (error) {
let title = "Unknown Error";
@@ -198,7 +202,7 @@ export class Chrome extends PosComponent {
}
}
return this.showPopup(ErrorTracebackPopup, { title, body, exitButtonIsShown: true });
return this.popup.add(ErrorTracebackPopup, { title, body, exitButtonIsShown: true });
}
registry.category("main_components").add("BlockUI", BlockUiFromRegistry);
@@ -290,7 +294,7 @@ export class Chrome extends PosComponent {
openCashControl() {
if (this.shouldShowCashControl()) {
this.showPopup(CashOpeningPopup, { keepBehind: true });
this.popup.add(CashOpeningPopup, { keepBehind: true });
}
}
@@ -302,52 +306,6 @@ export class Chrome extends PosComponent {
// EVENT HANDLERS //
_showStartScreen() {
const { name, props } = this.startScreen;
this.showScreen(name, props);
}
_getSavedScreen(order) {
return order.get_screen_data();
}
__showTempScreen(event) {
const { name, props, resolve } = event.detail;
this.pos.tempScreen = {
name,
component: registry.category("pos_screens").get(name),
props: { ...props, resolve },
};
}
__closeTempScreen() {
this.pos.tempScreen = null;
}
__showScreen({ detail: { name, props = {} } }) {
const component = registry.category("pos_screens").get(name);
// 1. Set the information of the screen to display.
this.pos.mainScreen = { name, component, props };
// 2. Save the screen to the order.
// - This screen is shown when the order is selected.
if (
!(component.prototype instanceof IndependentToOrderScreen) &&
name !== "ReprintReceiptScreen"
) {
this._setScreenData(name, props);
}
}
/**
* Set the latest screen to the current order. This is done so that
* when the order is selected again, the ui returns to the latest screen
* saved in the order.
*
* @param {string} name Screen name
* @param {Object} props props for the Screen component
*/
_setScreenData(name, props) {
const order = this.env.pos.get_order();
if (order) {
order.set_screen_data({ name, props });
}
}
async _closePos() {
// If pos is not properly loaded, we just go back to /web without
// doing anything in the order data.
@@ -379,7 +337,7 @@ export class Chrome extends PosComponent {
"not close the session before the issue " +
"has been resolved."
);
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Offline Orders"),
body: reason,
});
@@ -473,6 +431,17 @@ export class Chrome extends PosComponent {
get showCashMoveButton() {
return this.env.pos && this.env.pos.config && this.env.pos.config.cash_control;
}
/**
* Unmounts the tempScreen on error and dispatches the error in a separate
* stack so that it can be handled by the error service and display an error
* popup.
*
* @param {any} err the error that was thrown in the temp screen.
*/
onTempScreenError(err) {
this.pos.tempScreen = null;
Promise.reject(err);
}
}
Object.defineProperty(Chrome, "components", {
get() {
@@ -481,6 +450,7 @@ Object.defineProperty(Chrome, "components", {
Transition,
MainComponentsContainer,
WithEnv,
ErrorHandler,
Navbar,
},
PosComponent.components
@@ -3,8 +3,9 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { _lt } from "web.core";
import { renderToString } from "@web/core/utils/render";
import { CashMovePopup } from "../Popups/CashMovePopup";
import { ErrorPopup } from "../Popups/ErrorPopup";
import { CashMovePopup } from "@point_of_sale/js/Popups/CashMovePopup";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { useService } from "@web/core/utils/hooks";
const TRANSLATED_CASH_MOVE_TYPE = {
in: _lt("in"),
@@ -14,8 +15,14 @@ const TRANSLATED_CASH_MOVE_TYPE = {
export class CashMoveButton extends PosComponent {
static template = "point_of_sale.CashMoveButton";
setup() {
super.setup(...arguments);
this.popup = useService("popup");
this.notification = useService("pos_notification");
}
async onClick() {
const { confirmed, payload } = await this.showPopup(CashMovePopup);
const { confirmed, payload } = await this.popup.add(CashMovePopup);
if (!confirmed) {
return;
}
@@ -23,7 +30,7 @@ export class CashMoveButton extends PosComponent {
const translatedType = TRANSLATED_CASH_MOVE_TYPE[type];
const formattedAmount = this.env.pos.format_currency(amount);
if (!amount) {
return this.showNotification(
return this.notification.add(
_.str.sprintf(this.env._t("Cash in/out of %s is ignored."), formattedAmount),
3000
);
@@ -40,13 +47,13 @@ export class CashMoveButton extends PosComponent {
});
const printResult = await this.env.proxy.printer.print_receipt(renderedReceipt);
if (!printResult.successful) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: printResult.message.title,
body: printResult.message.body,
});
}
}
this.showNotification(
this.notification.add(
_.str.sprintf(this.env._t("Successfully made a cash %s of %s."), type, formattedAmount),
3000
);
@@ -1,15 +1,21 @@
/** @odoo-module */
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ClosePosPopup } from "../Popups/ClosePosPopup";
import { useService } from "@web/core/utils/hooks";
import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup";
// Previously HeaderButtonWidget
// This is the close session button
export class HeaderButton extends PosComponent {
static template = "HeaderButton";
setup() {
super.setup(...arguments);
this.popup = useService("popup");
}
async onClick() {
const info = await this.env.pos.getClosePosInfo();
this.showPopup(ClosePosPopup, { info: info, keepBehind: true });
this.popup.add(ClosePosPopup, { info: info, keepBehind: true });
}
}
@@ -1,12 +1,18 @@
/** @odoo-module */
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useService } from "@web/core/utils/hooks";
import { renderToString } from "@web/core/utils/render";
import { ErrorPopup } from "../Popups/ErrorPopup";
export class SaleDetailsButton extends PosComponent {
static template = "SaleDetailsButton";
setup() {
super.setup(...arguments);
this.popup = useService("popup");
}
async onClick() {
// IMPROVEMENT: Perhaps put this logic in a parent component
// so that for unit testing, we can check if this simple
@@ -25,7 +31,7 @@ export class SaleDetailsButton extends PosComponent {
);
const printResult = await this.env.proxy.printer.print_receipt(report);
if (!printResult.successful) {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: printResult.message.title,
body: printResult.message.body,
});
@@ -1,7 +1,8 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_store";
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScreen";
export class TicketButton extends PosComponent {
static template = "TicketButton";
@@ -13,11 +14,11 @@ export class TicketButton extends PosComponent {
if (this.isTicketScreenShown) {
this.env.posbus.trigger("ticket-button-clicked");
} else {
this.showScreen("TicketScreen");
this.pos.showScreen("TicketScreen");
}
}
get isTicketScreenShown() {
return this.pos.mainScreen.name === "TicketScreen";
return this.pos.mainScreen.component === TicketScreen;
}
get count() {
if (this.env.pos) {
+2 -36
View File
@@ -1,31 +1,7 @@
/** @odoo-module */
const { status } = owl;
/**
* This module bridges the data classes (such as those defined in
* models.js) to the view (Component) but not vice versa.
*
* The idea is to be able to perform side-effects to the user interface
* during calculation. Think of console.log during times we want to see
* the result of calculations. This is no different, except that instead
* of printing something in the console, we access a method in the user
* interface then the user interface reacts, e.g. calling `showPopup`.
*
* This however can be dangerous to the user interface as it can be possible
* that a rendered component is destroyed during the calculation. Because of
* this, we are going to limit external ui controls to those safe ones to
* use such as:
* - `showPopup`
* - `showTempScreen`
*
* IMPROVEMENT: After all, this Gui layer seems to be a good abstraction because
* there is a complete decoupling between data and view despite the data being
* able to use selected functionalities in the view layer. More formalized
* implementation is welcome.
*/
const config = {};
/**
* Call this when the user interface is ready. Provide the component
* that will be used to control the ui.
@@ -33,21 +9,11 @@ const config = {};
*/
export const configureGui = ({ component }) => {
config.component = component;
config.availableMethods = new Set([
"showScreen",
"showPopup",
"showTempScreen",
"playSound",
"setSyncStatus",
"showNotification",
]);
config.availableMethods = new Set(["setSyncStatus"]);
};
export class GuiNotReadyError extends Error {}
/**
* Import this and consume like so: `Gui.showPopup(<PopupName>, <props>)`.
* Like you would call `showPopup` in a component.
*/
// FIXME POSREF: remove this entirely.
export const Gui = new Proxy(config, {
get(target, key) {
const { component, availableMethods } = target;
@@ -1,6 +1,7 @@
/** @odoo-module */
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useService } from "@web/core/utils/hooks";
import { ConfirmPopup } from "../Popups/ConfirmPopup";
import { ErrorPopup } from "../Popups/ErrorPopup";
@@ -16,6 +17,7 @@ export class AbstractReceiptScreen extends PosComponent {
setup() {
super.setup();
this.orderReceipt = useRef("order-receipt");
this.popup = useService("popup");
}
async _printReceipt() {
if (this.env.proxy.printer) {
@@ -25,7 +27,7 @@ export class AbstractReceiptScreen extends PosComponent {
if (printResult.successful) {
return true;
} else {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: printResult.message.title,
body: "Do you want to print using the web printer?",
});
@@ -46,7 +48,7 @@ export class AbstractReceiptScreen extends PosComponent {
window.print();
return true;
} catch {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: this.env._t("Printing is not supported on some browsers"),
body: this.env._t(
"Printing is not supported on some browsers due to no default printing protocol " +
@@ -1,11 +1,17 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class IndependentToOrderScreen extends PosComponent {
static storeOnOrder = false;
setup() {
super.setup(...arguments);
this.pos = usePos();
}
close() {
const order = this.env.pos.get_order();
const { name: screenName } = order.get_screen_data();
this.showScreen(screenName);
this.pos.showScreen(screenName);
}
}
@@ -1,7 +1,7 @@
/** @odoo-module */
import { useExternalListener } from "@odoo/owl";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useBus } from "@web/core/utils/hooks";
/**
* Implement this abstract class by extending it like so:
@@ -22,7 +22,7 @@ import { useBus } from "@web/core/utils/hooks";
* The concrete popup can now be instantiated and be awaited for
* the user's response like so:
* ```js
* const { confirmed, payload } = await this.showPopup(ConcretePopup);
* const { confirmed, payload } = await this.popup.add(ConcretePopup);
* // based on the implementation above,
* // if confirmed, payload = 'result'
* // otherwise, payload = null
@@ -30,25 +30,24 @@ import { useBus } from "@web/core/utils/hooks";
*/
export class AbstractAwaitablePopup extends PosComponent {
setup() {
super.setup();
if (this.props.confirmKey) {
useBus(this.env.posbus, `confirm-popup-${this.props.id}`, this.confirm);
super.setup(...arguments);
useExternalListener(window, "keyup", this._onWindowKeyup);
}
_onWindowKeyup(event) {
if (!this.props.isActive || ["INPUT", "TEXTAREA"].includes(event.target.tagName)) {
return;
}
if (this.props.cancelKey) {
useBus(this.env.posbus, `cancel-popup-${this.props.id}`, this.cancel);
if (event.key === this.props.cancelKey) {
this.cancel();
} else if (event.key === this.props.confirmKey) {
this.confirm();
}
}
async confirm() {
this.env.posbus.trigger("close-popup", {
popupId: this.props.id,
response: { confirmed: true, payload: await this.getPayload() },
});
this.props.close({ confirmed: true, payload: await this.getPayload() });
}
cancel() {
this.env.posbus.trigger("close-popup", {
popupId: this.props.id,
response: { confirmed: false, payload: null },
});
this.props.close({ confirmed: false, payload: null });
}
/**
* Override this in the concrete popup implementation to set the
@@ -1,6 +1,7 @@
/** @odoo-module */
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { useService } from "@web/core/utils/hooks";
import { MoneyDetailsPopup } from "./MoneyDetailsPopup";
const { useState } = owl;
@@ -17,6 +18,7 @@ export class CashOpeningPopup extends AbstractAwaitablePopup {
notes: "",
openingCash: this.env.pos.pos_session.cash_register_balance_start || 0,
});
this.popup = useService("popup");
}
//@override
async confirm() {
@@ -30,7 +32,7 @@ export class CashOpeningPopup extends AbstractAwaitablePopup {
super.confirm();
}
async openDetailsPopup() {
const { confirmed, payload } = await this.showPopup(MoneyDetailsPopup, {
const { confirmed, payload } = await this.popup.add(MoneyDetailsPopup, {
moneyDetails: this.moneyDetails,
total: this.manualInputCashCount ? 0 : this.state.openingCash,
});
@@ -7,6 +7,8 @@ import { SaleDetailsButton } from "../ChromeWidgets/SaleDetailsButton";
import { ConfirmPopup } from "./ConfirmPopup";
import { ErrorPopup } from "./ErrorPopup";
import { MoneyDetailsPopup } from "./MoneyDetailsPopup";
import { AlertPopup } from "./AlertPopup";
import { useService } from "@web/core/utils/hooks";
const { useState } = owl;
@@ -16,6 +18,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
setup() {
super.setup();
this.popup = useService("popup");
this.manualInputCashCount = false;
this.cashControl = this.env.pos.config.cash_control;
this.closeSessionClicked = false;
@@ -31,7 +34,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
if (!this.cashControl || !this.hasDifference()) {
this.closeSession();
} else if (this.hasUserAuthority()) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Payments Difference"),
body: this.env._t(
"Do you want to accept payments difference and post a profit/loss journal entry?"
@@ -41,7 +44,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
this.closeSession();
}
} else {
await this.showPopup(ConfirmPopup, {
await this.popup.add(ConfirmPopup, {
title: this.env._t("Payments Difference"),
body: _.str.sprintf(
this.env._t(
@@ -61,7 +64,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
}
}
async openDetailsPopup() {
const { confirmed, payload } = await this.showPopup(MoneyDetailsPopup, {
const { confirmed, payload } = await this.popup.add(MoneyDetailsPopup, {
moneyDetails: this.moneyDetails,
total: this.manualInputCashCount
? 0
@@ -182,7 +185,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
throw error;
} else {
// FIXME POSREF: why are we catching errors here but not anywhere else in this method?
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: this.env._t("Closing session error"),
body: this.env._t(
"An error has occurred when trying to close the session.\n" +
@@ -201,14 +204,14 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
let title = "";
if (response.type == "alert") {
popupType = "AlertPopup";
popupType = AlertPopup;
title = response.title ? response.title : "";
} else {
popupType = "ErrorPopup";
popupType = ErrorPopup;
title = "Error";
}
await this.showPopup(popupType, { title: title, body: response.message });
await this.popup.add(popupType, { title: title, body: response.message });
if (response.redirect) {
window.location = "/web#action=point_of_sale.action_client_pos_menu";
}
@@ -26,7 +26,7 @@ const { useState } = owl;
*
* // supply the items to the popup and wait for user's response
* // when user pressed `confirm` in the popup, the changes he made will be returned by the showPopup function.
* const { confirmed, payload: newNames } = await this.showPopup(EditListPopup, {
* const { confirmed, payload: newNames } = await this.popup.add(EditListPopup, {
* title: "Can you confirm this item?",
* array: names })
*
@@ -2,6 +2,7 @@
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks";
// formerly ErrorPopupWidget
export class ErrorPopup extends AbstractAwaitablePopup {
@@ -16,8 +17,9 @@ export class ErrorPopup extends AbstractAwaitablePopup {
setup() {
super.setup();
owl.onMounted(this.onMounted);
this.sound = useService("sound");
}
onMounted() {
this.playSound("error");
this.sound.play("error");
}
}
@@ -3,8 +3,7 @@ import core from "web.core";
var _t = core._t;
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { Draggable } from "../Misc/Draggable";
@@ -44,7 +43,8 @@ export class NumberPopup extends AbstractAwaitablePopup {
.replace(".", this.decimalSeparator);
}
this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected });
numberBuffer.use({
this.numberBuffer = useService("number_buffer");
this.numberBuffer.use({
nonKeyboardInputEvent: "numpad-click-input",
triggerAtEnter: "accept-input",
triggerAtEscape: "close-this-popup",
@@ -65,7 +65,7 @@ export class NumberPopup extends AbstractAwaitablePopup {
}
}
confirm(event) {
if (numberBuffer.get()) {
if (this.numberBuffer.get()) {
super.confirm();
}
}
@@ -73,6 +73,6 @@ export class NumberPopup extends AbstractAwaitablePopup {
this.trigger("numpad-click-input", { key });
}
getPayload() {
return numberBuffer.get();
return this.numberBuffer.get();
}
}
@@ -19,6 +19,12 @@ export class OfflineErrorPopup extends ErrorPopup {
title: _lt("Offline Error"),
body: _lt("Either the server is inaccessible or browser is not connected online."),
};
setup() {
super.setup(...arguments);
if (this.constructor.dontShow) {
this.cancel();
}
}
dontShowAgain() {
this.constructor.dontShow = true;
@@ -1,122 +0,0 @@
/** @odoo-module */
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useBus } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { usePos } from "@point_of_sale/app/pos_store";
import { useExternalListener, useState } from "@odoo/owl";
/**
* This component is responsible in controlling the popups. It does so
* by coordinating with them thru the `env.posbus`. The basic steps follow:
* 1. `showPopup` method triggers `show-popup` event resulting to the
* mounting of the requested popup.
* 2. When the popup is shown, the `confirm`/`cancel` method of the popup
* will be called after the popup is used. `confirming`/`cancelling`
* will trigger the `close-popup`, which this component also listens to,
* resulting to closing of the popup.
*
* Furthermore, Pressing `confirmKey`/`cancelKey` which defaults to
* 'Enter'/'Escape', will automatically `confirm`/`cancel` the `topPopup`.
* This behavior is accomplished by listening to `keyup` event of the window.
* When the `confirmKey`/`cancelKey` of the `topPopup` is pressed,
* 'cancel-popup-{top-popup-id}'/'confirm-popup-{top-popup-id}' will be triggered
* and since the popup is listening to that event (@see AbstractAwaitablePopup),
* it will result to the call of `confirm`/`cancel` method.
*
* @typedef {{ id: number, resolve: Function, keepBehind?: boolean, cancelKey?: string, confirmKey?: string }} BasePopupProps
* @typedef {{ name: string, component: AbstractAwaitablePopup, props: BasePopupProps, key: string }} Popup
*/
export class PosPopupController extends PosComponent {
static template = "point_of_sale.PosPopupController";
static components = {};
setup() {
this.pos = usePos();
// FIXME POSREF stop overwriting env
this.env = this.pos.legacyEnv;
this.__owl__.childEnv = this.env;
super.setup();
useBus(this.env.posbus, "show-popup", this._showPopup);
useBus(this.env.posbus, "close-popup", this._closePopup);
useExternalListener(window, "keyup", this._onWindowKeyup);
this.popups = useState([]);
}
/**
* A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default.
* Also, if it's not the top popup, it is hidden from the view.
* This can be overridden by the default props of the popop component
* and the props used in requesting to show the popup.
*
* @param {AbstractAwaitablePopup} popupComponent
* @param {Object} props
* @returns {BasePopupProps}
*/
_showPopup(event) {
let { id, component, props, resolve } = event.detail;
props = Object.assign(props || {}, { id, resolve });
if (component.dontShow) {
resolve();
return;
}
this.popups.push({
component,
props: {
keepBehind: false,
cancelKey: "Escape",
confirmKey: "Enter",
// FIXME POSREF assigning the default props by hand defeats the point of default props
...component.defaultProps,
...props,
},
});
}
_closePopup(event) {
const { popupId, response } = event.detail;
const index = this.popups.findIndex((popup) => popup.props.id == popupId);
if (index != -1) {
const popup = this.popups[index];
popup.props.resolve(response);
this.popups.splice(index, 1);
}
}
_onWindowKeyup(event) {
const eventIsFromInputField =
event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA";
const shouldHandleKey = this.topPopup && !eventIsFromInputField;
if (!shouldHandleKey) {
return;
}
if (event.key === this.topPopup.props.cancelKey) {
this.env.posbus.trigger(`cancel-popup-${this.topPopup.props.id}`);
} else if (event.key === this.topPopup.props.confirmKey) {
this.env.posbus.trigger(`confirm-popup-${this.topPopup.props.id}`);
}
}
/**
* @returns {boolean} Hide the element of this component when this returns false.
*/
isShown() {
return this.popups.length > 0;
}
get topPopup() {
return this.popups[this.popups.length - 1];
}
/**
* By default, only show the top popup. But always show a popup if
* `keepBehind` props is true. Meaning, if you have 2 popups, and
* the bottom popup has `keepBehind = true`, then the bottom popup
* will be visible if it's not blocked in the view by the top popup.
*
* @param {Popup} popup
* @returns {boolean}
*/
shouldShow(popup) {
return this.topPopup === popup || popup.props.keepBehind;
}
}
registry.category("main_components").add("PosPopupController", {
Component: PosPopupController,
});
@@ -1,54 +1,9 @@
/** @odoo-module */
import { useService } from "@web/core/utils/hooks";
import { LegacyComponent } from "@web/legacy/legacy_component";
let nextId = 0;
export class PosComponent extends LegacyComponent {
static components = {};
setup() {
this.notification = useService("pos_notification");
this.sound = useService("sound");
}
/**
* This function is available to all Components that inherit this class.
* The goal of this function is to show an awaitable dialog (popup) that
* returns a response after user interaction. See the following for quick
* demonstration:
*
* ```
* async getUserName() {
* const userResponse = await this.showPopup(TextInputPopup, { title: 'What is your name?' });
* // at this point, the TextInputPopup is displayed. Depending on how the popup is defined,
* // say the input contains the name, the result of the interaction with the user is
* // saved in `userResponse`.
* console.log(userResponse); // logs { confirmed: true, payload: <name> }
* }
* ```
*
* @param {String} name Name of the popup component
* @param {Object} props Object that will be used to render to popup
*/
showPopup(component, props) {
return new Promise((resolve) => {
this.env.posbus.trigger("show-popup", { component, props, resolve, id: nextId++ });
});
}
showTempScreen(name, props) {
return new Promise((resolve) => {
this.trigger("show-temp-screen", { name, props, resolve });
});
}
showScreen(name, props) {
this.trigger("show-main-screen", { name, props });
}
/**
* @param {String} name 'bell' | 'error'
*/
playSound(name) {
this.sound.play(name);
}
/**
* Control the SyncNotification component.
* @param {String} status 'connected' | 'connecting' | 'disconnected' | 'error'
@@ -57,7 +12,4 @@ export class PosComponent extends LegacyComponent {
setSyncStatus(status, pending) {
this.trigger("set-sync-status", { status, pending });
}
showNotification(message, duration) {
this.notification.add(message, duration);
}
}
@@ -4,6 +4,7 @@ import { _t } from "web.core";
import { getDataURLFromFile } from "web.utils";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { useService } from "@web/core/utils/hooks";
const { onMounted, onWillUnmount } = owl;
@@ -12,6 +13,7 @@ export class PartnerDetailsEdit extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
this.intFields = ["country_id", "state_id", "property_product_pricelist"];
const partner = this.props.partner;
this.changes = {
@@ -66,7 +68,7 @@ export class PartnerDetailsEdit extends PosComponent {
}
}
if ((!this.props.partner.name && !processedChanges.name) || processedChanges.name === "") {
return this.showPopup(ErrorPopup, {
return this.popup.add(ErrorPopup, {
title: _t("A Customer Name Is Required"),
});
}
@@ -76,7 +78,7 @@ export class PartnerDetailsEdit extends PosComponent {
async uploadImage(event) {
const file = event.target.files[0];
if (!file.type.match(/image.*/)) {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: this.env._t("Unsupported File Format"),
body: this.env._t(
"Only web-compatible Image formats such as .png or .jpeg are supported."
@@ -124,7 +126,7 @@ export class PartnerDetailsEdit extends PosComponent {
const img = new Image();
img.addEventListener("load", () => resolve(img));
img.addEventListener("error", () => {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Loading Image Error"),
body: this.env._t("Encountered error when loading image. Please try again."),
});
@@ -4,10 +4,11 @@ import { PosComponent } from "@point_of_sale/js/PosComponent";
import { registry } from "@web/core/registry";
import { debounce } from "@web/core/utils/timing";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { PartnerLine } from "./PartnerLine";
import { PartnerDetailsEdit } from "./PartnerDetailsEdit";
import { usePos } from "@point_of_sale/app/pos_hook";
const { onWillUnmount, useRef } = owl;
@@ -32,6 +33,8 @@ export class PartnerListScreen extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
this.notification = useService("pos_notification");
useListener("click-save", () => this.env.bus.trigger("save-partner"));
useListener("save-changes", this.saveChanges);
this.searchWordInputRef = useRef("search-word-input-partner");
@@ -59,12 +62,12 @@ export class PartnerListScreen extends PosComponent {
this.render(true);
} else {
this.props.resolve({ confirmed: false, payload: false });
this.trigger("close-temp-screen");
this.pos.closeTempScreen();
}
}
confirm() {
this.props.resolve({ confirmed: true, payload: this.state.selectedPartner });
this.trigger("close-temp-screen");
this.pos.closeTempScreen();
}
activateEditMode() {
this.state.detailIsShown = true;
@@ -112,7 +115,7 @@ export class PartnerListScreen extends PosComponent {
return;
}
const result = await this.searchPartner();
this.showNotification(
this.notification.add(
_.str.sprintf(
this.env._t('%s customer(s) found for "%s".'),
result.length,
@@ -3,9 +3,8 @@
import { parse } from "web.field_utils";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useErrorHandlers } from "@point_of_sale/js/custom_hooks";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { registry } from "@web/core/registry";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import utils from "web.utils";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
@@ -16,6 +15,7 @@ import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { PaymentScreenNumpad } from "./PaymentScreenNumpad";
import { PaymentScreenPaymentLines } from "./PaymentScreenPaymentLines";
import { PaymentScreenStatus } from "./PaymentScreenStatus";
import { usePos } from "@point_of_sale/app/pos_hook";
export class PaymentScreen extends PosComponent {
static template = "PaymentScreen";
@@ -27,6 +27,9 @@ export class PaymentScreen extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
this.popup = useService("popup");
this.notification = useService("pos_notification");
useListener("delete-payment-line", this.deletePaymentLine);
useListener("select-payment-line", this.selectPaymentLine);
useListener("new-payment-line", this.addNewPaymentLine);
@@ -39,14 +42,15 @@ export class PaymentScreen extends PosComponent {
this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) =>
this.env.pos.config.payment_method_ids.includes(method.id)
);
numberBuffer.use(this._getNumberBufferConfig);
this.numberBuffer = useService("number_buffer");
this.numberBuffer.use(this._getNumberBufferConfig);
useErrorHandlers();
this.payment_interface = null;
this.error = false;
}
showMaxValueError() {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Maximum value reached"),
body: this.env._t(
"The amount cannot be higher than the due amount if you don't have a cash payment method configured."
@@ -87,9 +91,12 @@ export class PaymentScreen extends PosComponent {
// IMPROVEMENT: This code snippet is repeated multiple times.
// Maybe it's better to create a function for it.
const currentPartner = this.currentOrder.get_partner();
const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", {
partner: currentPartner,
});
const { confirmed, payload: newPartner } = await this.pos.showTempScreen(
"PartnerListScreen",
{
partner: currentPartner,
}
);
if (confirmed) {
this.currentOrder.set_partner(newPartner);
this.currentOrder.updatePricelist(newPartner);
@@ -99,10 +106,10 @@ export class PaymentScreen extends PosComponent {
// original function: click_paymentmethods
const result = this.currentOrder.add_paymentline(paymentMethod);
if (result) {
numberBuffer.reset();
this.numberBuffer.reset();
return true;
} else {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Error"),
body: this.env._t("There is already an electronic payment in progress."),
});
@@ -124,10 +131,10 @@ export class PaymentScreen extends PosComponent {
) {
return;
}
if (numberBuffer.get() === null) {
if (this.numberBuffer.get() === null) {
this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } });
} else {
this.selectedPaymentLine.set_amount(numberBuffer.getFloat());
this.selectedPaymentLine.set_amount(this.numberBuffer.getFloat());
}
}
toggleIsToInvoice() {
@@ -144,7 +151,7 @@ export class PaymentScreen extends PosComponent {
const change = this.currentOrder.get_change();
const value = tip === 0 && change > 0 ? change : tip;
const { confirmed, payload } = await this.showPopup(NumberPopup, {
const { confirmed, payload } = await this.popup.add(NumberPopup, {
title: tip ? this.env._t("Change Tip") : this.env._t("Add Tip"),
startingValue: value,
isInputSelected: true,
@@ -156,7 +163,7 @@ export class PaymentScreen extends PosComponent {
}
async toggleShippingDatePicker() {
if (!this.currentOrder.getShippingDate()) {
const { confirmed, payload: shippingDate } = await this.showPopup(DatePickerPopup, {
const { confirmed, payload: shippingDate } = await this.popup.add(DatePickerPopup, {
title: this.env._t("Select the shipping date"),
});
if (confirmed) {
@@ -180,12 +187,12 @@ export class PaymentScreen extends PosComponent {
.send_payment_cancel(this.currentOrder, cid)
.then(function () {
self.currentOrder.remove_paymentline(line);
numberBuffer.reset();
this.numberBuffer.reset();
self.render(true);
});
} else if (line.get_payment_status() !== "waitingCancel") {
this.currentOrder.remove_paymentline(line);
numberBuffer.reset();
this.numberBuffer.reset();
this.render(true);
}
}
@@ -193,13 +200,13 @@ export class PaymentScreen extends PosComponent {
const { cid } = event.detail;
const line = this.paymentLines.find((line) => line.cid === cid);
this.currentOrder.select_paymentline(line);
numberBuffer.reset();
this.numberBuffer.reset();
this.render(true);
}
async validateOrder(isForceValidate) {
if (this.env.pos.config.cash_rounding) {
if (!this.env.pos.get_order().check_paymentlines_rounding()) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Rounding error in payment lines"),
body: this.env._t(
"The amount of your payment lines must be rounded to validate the transaction."
@@ -259,7 +266,7 @@ export class PaymentScreen extends PosComponent {
syncOrderResult.map((res) => res.id)
);
if (!postPushResult) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Error: no internet connection."),
body: this.env._t(
"Some, if not all, post-processing after syncing order failed."
@@ -284,14 +291,14 @@ export class PaymentScreen extends PosComponent {
} finally {
// Always show the next screen regardless of error since pos has to
// continue working even offline.
this.showScreen(this.nextScreen);
this.pos.showScreen(this.nextScreen);
// Remove the order from the local storage so that when we refresh the page, the order
// won't be there
this.env.pos.db.remove_unpaid_order(this.currentOrder);
// Ask the user to sync the remaining unsynced orders.
if (!hasError && syncOrderResult && this.env.pos.db.get_orders().length) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Remaining unsynced orders"),
body: this.env._t(
"There are unsynced orders. Do you want to sync these orders?"
@@ -322,7 +329,7 @@ export class PaymentScreen extends PosComponent {
}
async _isOrderValid(isForceValidate) {
if (this.currentOrder.get_orderlines().length === 0 && this.currentOrder.is_to_invoice()) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Empty Order"),
body: this.env._t(
"There must be at least one product in your order before it can be validated and invoiced."
@@ -336,7 +343,7 @@ export class PaymentScreen extends PosComponent {
);
if (splitPayments.length && !this.currentOrder.get_partner()) {
const paymentMethod = splitPayments[0].payment_method;
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Customer Required"),
body: _.str.sprintf(
this.env._t("Customer is required for %s payment method."),
@@ -353,7 +360,7 @@ export class PaymentScreen extends PosComponent {
(this.currentOrder.is_to_invoice() || this.currentOrder.getShippingDate()) &&
!this.currentOrder.get_partner()
) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Please select the Customer"),
body: this.env._t(
"You need to select the customer before you can invoice or ship an order."
@@ -370,7 +377,7 @@ export class PaymentScreen extends PosComponent {
this.currentOrder.getShippingDate() &&
!(partner.name && partner.street && partner.city && partner.country_id)
) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Incorrect address for shipping"),
body: this.env._t("The selected customer needs an address."),
});
@@ -381,7 +388,7 @@ export class PaymentScreen extends PosComponent {
this.currentOrder.get_total_with_tax() != 0 &&
this.currentOrder.get_paymentlines().length === 0
) {
this.showNotification(this.env._t("Select a payment method to validate the order."));
this.notification.add(this.env._t("Select a payment method to validate the order."));
return false;
}
@@ -391,7 +398,7 @@ export class PaymentScreen extends PosComponent {
if (this.currentOrder.has_not_valid_rounding()) {
var line = this.currentOrder.has_not_valid_rounding();
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Incorrect rounding"),
body: this.env._t(
"You have to round your payments lines." + line.amount + " is not rounded."
@@ -413,7 +420,7 @@ export class PaymentScreen extends PosComponent {
cash = cash || this.env.pos.payment_methods[i].is_cash_count;
}
if (!cash) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Cannot return change without a cash payment method"),
body: this.env._t(
"There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration"
@@ -429,23 +436,25 @@ export class PaymentScreen extends PosComponent {
this.currentOrder.get_total_with_tax() > 0 &&
this.currentOrder.get_total_with_tax() * 1000 < this.currentOrder.get_total_paid()
) {
this.showPopup(ConfirmPopup, {
title: this.env._t("Please Confirm Large Amount"),
body:
this.env._t("Are you sure that the customer wants to pay") +
" " +
this.env.pos.format_currency(this.currentOrder.get_total_paid()) +
" " +
this.env._t("for an order of") +
" " +
this.env.pos.format_currency(this.currentOrder.get_total_with_tax()) +
" " +
this.env._t('? Clicking "Confirm" will validate the payment.'),
}).then(({ confirmed }) => {
if (confirmed) {
this.validateOrder(true);
}
});
this.popup
.add(ConfirmPopup, {
title: this.env._t("Please Confirm Large Amount"),
body:
this.env._t("Are you sure that the customer wants to pay") +
" " +
this.env.pos.format_currency(this.currentOrder.get_total_paid()) +
" " +
this.env._t("for an order of") +
" " +
this.env.pos.format_currency(this.currentOrder.get_total_with_tax()) +
" " +
this.env._t('? Clicking "Confirm" will validate the payment.'),
})
.then(({ confirmed }) => {
if (confirmed) {
this.validateOrder(true);
}
});
return false;
}
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { TextAreaPopup } from "@point_of_sale/js/Popups/TextAreaPopup";
export class OrderlineCustomerNoteButton extends PosComponent {
@@ -10,6 +10,7 @@ export class OrderlineCustomerNoteButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("click", this.onClick);
}
async onClick() {
@@ -17,8 +18,7 @@ export class OrderlineCustomerNoteButton extends PosComponent {
if (!selectedOrderline) {
return;
}
const { confirmed, payload: inputNote } = await this.showPopup(TextAreaPopup, {
const { confirmed, payload: inputNote } = await this.popup.add(TextAreaPopup, {
startingValue: selectedOrderline.get_customer_note(),
title: this.env._t("Add Customer Note"),
});
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { ProductInfoPopup } from "@point_of_sale/js/Popups/ProductInfoPopup";
export class ProductInfoButton extends PosComponent {
@@ -11,6 +11,7 @@ export class ProductInfoButton extends PosComponent {
setup() {
super.setup();
useListener("click", this.onClick);
this.popup = useService("popup");
}
async onClick() {
const orderline = this.env.pos.get_order().get_selected_orderline();
@@ -18,7 +19,7 @@ export class ProductInfoButton extends PosComponent {
const product = orderline.get_product();
const quantity = orderline.get_quantity();
const info = await this.env.pos.getProductInfo(product, quantity);
this.showPopup(ProductInfoPopup, { info: info, product: product });
this.popup.add(ProductInfoPopup, { info: info, product: product });
}
}
}
@@ -1,5 +1,6 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
@@ -9,12 +10,13 @@ export class RefundButton extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
useListener("click", this._onClick);
}
_onClick() {
const partner = this.env.pos.get_order().get_partner();
const searchDetails = partner ? { fieldName: "PARTNER", searchTerm: partner.name } : {};
this.showScreen("TicketScreen", {
this.pos.showScreen("TicketScreen", {
ui: { filter: "SYNCED", searchDetails },
destinationOrder: this.env.pos.get_order(),
});
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
export class SetFiscalPositionButton extends PosComponent {
@@ -10,6 +10,7 @@ export class SetFiscalPositionButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("click", this.onClick);
}
get currentOrder() {
@@ -39,7 +40,7 @@ export class SetFiscalPositionButton extends PosComponent {
item: fiscalPos,
});
}
const { confirmed, payload: selectedFiscalPosition } = await this.showPopup(
const { confirmed, payload: selectedFiscalPosition } = await this.popup.add(
SelectionPopup,
{
title: this.env._t("Select Fiscal Position"),
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
export class SetPricelistButton extends PosComponent {
@@ -10,6 +10,7 @@ export class SetPricelistButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("click", this.onClick);
}
get currentOrder() {
@@ -40,7 +41,7 @@ export class SetPricelistButton extends PosComponent {
});
}
const { confirmed, payload: selectedPricelist } = await this.showPopup(SelectionPopup, {
const { confirmed, payload: selectedPricelist } = await this.popup.add(SelectionPopup, {
title: this.env._t("Select the pricelist"),
list: selectionList,
});
@@ -1,6 +1,6 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup";
@@ -15,13 +15,16 @@ export class OrderWidget extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("select-line", this._selectLine);
useListener("edit-pack-lot-lines", this._editPackLotLines);
this.scrollableRef = useRef("scrollable");
useEffect(
() => {
const selectedLineEl = this.scrollableRef.el && this.scrollableRef.el.querySelector(".orderline.selected");
if(selectedLineEl) {
const selectedLineEl =
this.scrollableRef.el &&
this.scrollableRef.el.querySelector(".orderline.selected");
if (selectedLineEl) {
selectedLineEl.scrollIntoView({ behavior: "smooth", block: "start" });
}
},
@@ -48,7 +51,7 @@ export class OrderWidget extends PosComponent {
const orderline = event.detail.orderline;
const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot();
const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot);
const { confirmed, payload } = await this.showPopup(EditListPopup, {
const { confirmed, payload } = await this.popup.add(EditListPopup, {
title: this.env._t("Lot/Serial Number(s) Required"),
name: orderline.product.display_name,
isSingleItem: isAllowOnlyOneLot,
@@ -2,9 +2,8 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ControlButtonsMixin } from "@point_of_sale/js/ControlButtonsMixin";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { registry } from "@web/core/registry";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { useBarcodeReader } from "@point_of_sale/js/custom_hooks";
import { parse } from "web.field_utils";
@@ -21,6 +20,7 @@ import { MobileOrderWidget } from "../../Misc/MobileOrderWidget";
import { NumpadWidget } from "./NumpadWidget";
import { OrderWidget } from "./OrderWidget";
import { ProductsWidget } from "./ProductsWidget";
import { usePos } from "@point_of_sale/app/pos_hook";
const { onMounted, useState } = owl;
@@ -36,6 +36,8 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
setup() {
super.setup();
this.pos = usePos();
this.popup = useService("popup");
useListener("update-selected-orderline", this._updateSelectedOrderline);
useListener("select-line", this._selectLine);
useListener("set-numpad-mode", this._setNumpadMode);
@@ -50,7 +52,8 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
discount: this._barcodeDiscountAction,
error: this._barcodeErrorAction,
});
numberBuffer.use({
this.numberBuffer = useService("number_buffer");
this.numberBuffer.use({
nonKeyboardInputEvent: "numpad-click-input",
triggerAtInput: "update-selected-orderline",
useWithBarcode: true,
@@ -59,7 +62,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
// Call `reset` when the `onMounted` callback in `numberBuffer.use` is done.
// We don't do this in the `mounted` lifecycle method because it is called before
// the callbacks in `onMounted` hook.
onMounted(() => numberBuffer.reset());
onMounted(() => this.numberBuffer.reset());
this.state = useState({
mobile_pane: this.props.mobile_pane || "right",
});
@@ -90,7 +93,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
product.attribute_line_ids,
(id) => this.env.pos.attributes_by_ptal_id[id]
).filter((attr) => attr !== undefined);
const { confirmed, payload } = await this.showPopup(ProductConfiguratorPopup, {
const { confirmed, payload } = await this.popup.add(ProductConfiguratorPopup, {
product: product,
attributes: attributes,
});
@@ -123,7 +126,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
packLotLinesToEdit = [];
}
}
const { confirmed, payload } = await this.showPopup(EditListPopup, {
const { confirmed, payload } = await this.popup.add(EditListPopup, {
title: this.env._t("Lot/Serial Number(s) Required"),
name: product.display_name,
isSingleItem: isAllowOnlyOneLot,
@@ -149,7 +152,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
if (product.to_weight && this.env.pos.config.iface_electronic_scale) {
// Show the ScaleScreen to weigh the product.
if (this.isScaleAvailable) {
const { confirmed, payload } = await this.showTempScreen("ScaleScreen", {
const { confirmed, payload } = await this.pos.showTempScreen("ScaleScreen", {
product,
});
if (confirmed) {
@@ -184,16 +187,16 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
}
// Add the product after having the extra information.
await this._addProduct(product, options);
numberBuffer.reset();
this.numberBuffer.reset();
}
_setNumpadMode(event) {
const { mode } = event.detail;
numberBuffer.capture();
numberBuffer.reset();
this.numberBuffer.capture();
this.numberBuffer.reset();
this.env.pos.numpadMode = mode;
}
_selectLine() {
numberBuffer.reset();
this.numberBuffer.reset();
}
async _updateSelectedOrderline(event) {
if (this.env.pos.numpadMode === "quantity" && this.env.pos.disallowLineQuantityChange()) {
@@ -210,7 +213,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
.get_quantity();
if (selectedLine.noDecrease) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Invalid action"),
body: this.env._t("You are not allowed to change this quantity"),
});
@@ -229,7 +232,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
const val = buffer === null ? "remove" : buffer;
this._setValue(val);
if (val == "remove") {
numberBuffer.reset();
this.numberBuffer.reset();
this.env.pos.numpadMode = "quantity";
}
}
@@ -239,7 +242,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
if (this.env.pos.numpadMode === "quantity") {
const result = this.currentOrder.get_selected_orderline().set_quantity(val);
if (!result) {
numberBuffer.reset();
this.numberBuffer.reset();
}
} else if (this.env.pos.numpadMode === "discount") {
this.currentOrder.get_selected_orderline().set_discount(val);
@@ -319,7 +322,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
// Why? Because once we start declaring barcode actions in different
// screens, these methods will also be declared over and over.
_barcodeErrorAction(code) {
this.showPopup(ErrorBarcodePopup, { code: this._codeRepr(code) });
this.popup.add(ErrorBarcodePopup, { code: this._codeRepr(code) });
}
_codeRepr(code) {
if (code.code.length > 32) {
@@ -329,7 +332,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
}
}
async _displayAllControlPopup() {
await this.showPopup(ControlButtonPopup, {
await this.popup.add(ControlButtonPopup, {
controlButtons: this.controlButtons,
});
}
@@ -339,7 +342,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
*/
async _onScaleNotAvailable() {}
async _showDecreaseQuantityPopup() {
const { confirmed, payload: inputNumber } = await this.showPopup(NumberPopup, {
const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, {
startingValue: 0,
title: this.env._t("Set the new quantity"),
});
@@ -370,7 +373,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
// IMPROVEMENT: This code snippet is very similar to selectPartner of PaymentScreen.
const currentPartner = this.currentOrder.get_partner();
if (currentPartner && this.currentOrder.getHasRefundLines()) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Can't change customer"),
body: _.str.sprintf(
this.env._t(
@@ -381,9 +384,12 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
});
return;
}
const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", {
partner: currentPartner,
});
const { confirmed, payload: newPartner } = await this.pos.showTempScreen(
"PartnerListScreen",
{
partner: currentPartner,
}
);
if (confirmed) {
this.currentOrder.set_partner(newPartner);
this.currentOrder.updatePricelist(newPartner);
@@ -400,7 +406,7 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
(this.env.pos.picking_type.use_create_lots ||
this.env.pos.picking_type.use_existing_lots)
) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Some Serial/Lot Numbers are missing"),
body: this.env._t(
"You are trying to sell products with serial/lot numbers, but some of them are not set.\nWould you like to proceed anyway?"
@@ -409,10 +415,10 @@ export class ProductScreen extends ControlButtonsMixin(PosComponent) {
cancelText: this.env._t("No"),
});
if (confirmed) {
this.showScreen("PaymentScreen");
this.pos.showScreen("PaymentScreen");
}
} else {
this.showScreen("PaymentScreen");
this.pos.showScreen("PaymentScreen");
}
}
switchPane() {
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useListener } from "@web/core/utils/hooks";
import { usePos } from "@point_of_sale/app/pos_store";
import { usePos } from "@point_of_sale/app/pos_hook";
import { ProductItem } from "./ProductItem";
import { ProductsWidgetControlPanel } from "./ProductsWidgetControlPanel";
@@ -64,10 +64,7 @@ export class ProductsWidget extends PosComponent {
return this.env.pos.db.get_category_childs_ids(0).length === 0;
}
get shouldShowButton() {
return (
this.productsToDisplay.length === 0 &&
this.searchWord
);
return this.productsToDisplay.length === 0 && this.searchWord;
}
_switchCategory(event) {
this.env.pos.setSelectedCategoryId(event.detail);
@@ -2,11 +2,12 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { debounce } from "@web/core/utils/timing";
import { usePos } from "@point_of_sale/app/pos_store";
import { usePos } from "@point_of_sale/app/pos_hook";
import { CategoryButton } from "./CategoryButton";
import { onMounted, onWillUnmount, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
export class ProductsWidgetControlPanel extends PosComponent {
static components = { CategoryButton };
@@ -15,6 +16,7 @@ export class ProductsWidgetControlPanel extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
this.notification = useService("pos_notification");
this.updateSearch = debounce(this.updateSearch, 100);
this.state = useState({ searchInput: "", mobileSearchBarIsShown: false });
@@ -50,7 +52,7 @@ export class ProductsWidgetControlPanel extends PosComponent {
return;
}
const result = await this.loadProductFromDB();
this.showNotification(
this.notification.add(
_.str.sprintf(
this.env._t('%s product(s) found for "%s".'),
result.length,
@@ -9,6 +9,7 @@ import { registry } from "@web/core/registry";
import { OrderReceipt } from "./OrderReceipt";
import { onMounted, useRef, status } from "@odoo/owl";
import { usePos } from "@point_of_sale/app/pos_hook";
export class ReceiptScreen extends AbstractReceiptScreen {
static template = "ReceiptScreen";
@@ -16,6 +17,7 @@ export class ReceiptScreen extends AbstractReceiptScreen {
setup() {
super.setup();
this.pos = usePos();
useErrorHandlers();
this.orderReceipt = useRef("order-receipt");
const order = this.currentOrder;
@@ -57,9 +59,7 @@ export class ReceiptScreen extends AbstractReceiptScreen {
this.orderUiState.emailNotice = this.env._t("Email sent.");
} catch {
this.orderUiState.emailSuccessful = false;
this.orderUiState.emailNotice = this.env._t(
"Sending email failed. Please try again."
);
this.orderUiState.emailNotice = this.env._t("Sending email failed. Please try again.");
}
}
get orderAmountPlusTip() {
@@ -104,7 +104,7 @@ export class ReceiptScreen extends AbstractReceiptScreen {
this.env.pos.removeOrder(this.currentOrder);
this._addNewOrder();
const { name, props } = this.nextScreen;
this.showScreen(name, props);
this.pos.showScreen(name, props);
if (this.env.pos.config.iface_customer_facing_display) {
this.env.pos.send_current_order_to_customer_facing_display();
}
@@ -3,6 +3,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { round_precision as round_pr } from "web.utils";
import { registry } from "@web/core/registry";
import { usePos } from "@point_of_sale/app/pos_hook";
const { onMounted, onWillUnmount, useExternalListener, useState } = owl;
@@ -19,6 +20,7 @@ export class ScaleScreen extends PosComponent {
this.state = useState({ weight: 0 });
onMounted(this.onMounted);
onWillUnmount(this.onWillUnmount);
this.pos = usePos();
}
onMounted() {
// start the scale reading
@@ -30,14 +32,14 @@ export class ScaleScreen extends PosComponent {
}
back() {
this.props.resolve({ confirmed: false, payload: null });
this.trigger("close-temp-screen");
this.pos.closeTempScreen();
}
confirm() {
this.props.resolve({
confirmed: true,
payload: { weight: this.state.weight },
});
this.trigger("close-temp-screen");
this.pos.closeTempScreen();
}
_onHotkeys(event) {
if (event.key === "Escape") {
@@ -1,15 +1,18 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { usePos } from "@point_of_sale/app/pos_hook";
export class InvoiceButton extends PosComponent {
static template = "InvoiceButton";
setup() {
super.setup();
this.pos = usePos();
this.popup = useService("popup");
useListener("click", this._onClick);
}
get isAlreadyInvoiced() {
@@ -45,7 +48,7 @@ export class InvoiceButton extends PosComponent {
throw error;
} else {
// NOTE: error here is most probably undefined
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Network Error"),
body: this.env._t("Unable to download invoice."),
});
@@ -69,7 +72,7 @@ export class InvoiceButton extends PosComponent {
// Part 1: Handle missing partner.
// Write to pos.order the selected partner.
if (!order.get_partner()) {
const { confirmed: confirmedPopup } = await this.showPopup(ConfirmPopup, {
const { confirmed: confirmedPopup } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Need customer to invoice"),
body: this.env._t("Do you want to open the customer list to select customer?"),
});
@@ -78,7 +81,7 @@ export class InvoiceButton extends PosComponent {
}
const { confirmed: confirmedTempScreen, payload: newPartner } =
await this.showTempScreen("PartnerListScreen");
await this.pos.showTempScreen("PartnerListScreen");
if (!confirmedTempScreen) {
return;
}
@@ -2,18 +2,20 @@
import { useListener } from "@web/core/utils/hooks";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { usePos } from "@point_of_sale/app/pos_hook";
export class ReprintReceiptButton extends PosComponent {
static template = "ReprintReceiptButton";
setup() {
super.setup();
this.pos = usePos();
useListener("click", this._onClick);
}
async _onClick() {
if (!this.props.order) {
return;
}
this.showScreen("ReprintReceiptScreen", { order: this.props.order });
this.pos.showScreen("ReprintReceiptScreen", { order: this.props.order });
}
}
@@ -1,5 +1,6 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_hook";
import { AbstractReceiptScreen } from "@point_of_sale/js/Misc/AbstractReceiptScreen";
import { registry } from "@web/core/registry";
import { OrderReceipt } from "../ReceiptScreen/OrderReceipt";
@@ -7,21 +8,23 @@ import { OrderReceipt } from "../ReceiptScreen/OrderReceipt";
export class ReprintReceiptScreen extends AbstractReceiptScreen {
static template = "ReprintReceiptScreen";
static components = { OrderReceipt };
static storeOnOrder = false;
setup() {
super.setup();
this.pos = usePos();
owl.onMounted(this.onMounted);
}
onMounted() {
this.printReceipt();
}
confirm() {
this.showScreen("TicketScreen", { reuseSavedUIState: true });
this.pos.showScreen("TicketScreen", { reuseSavedUIState: true });
}
async printReceipt() {
if (this.env.proxy.printer && this.env.pos.config.iface_print_skip_screen) {
const result = await this._printReceipt();
if (result) {
this.showScreen("TicketScreen", { reuseSavedUIState: true });
this.pos.showScreen("TicketScreen", { reuseSavedUIState: true });
}
}
}
@@ -2,9 +2,8 @@
import { Order } from "@point_of_sale/js/models";
import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { registry } from "@web/core/registry";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { parse } from "web.field_utils";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
@@ -16,6 +15,7 @@ import { NumpadWidget } from "../ProductScreen/NumpadWidget";
import { OrderDetails } from "../TicketScreen/OrderDetails";
import { ReprintReceiptButton } from "./ControlButtons/ReprintReceiptButton";
import { SearchBar } from "../../Misc/SearchBar";
import { usePos } from "@point_of_sale/app/pos_hook";
const { onMounted, onWillUnmount, useState } = owl;
@@ -41,6 +41,8 @@ export class TicketScreen extends IndependentToOrderScreen {
setup() {
super.setup();
this.pos = usePos();
this.popup = useService("popup");
useListener("close-screen", this._onCloseScreen);
useListener("filter-selected", this._onFilterSelected);
useListener("search", this._onSearch);
@@ -54,7 +56,8 @@ export class TicketScreen extends IndependentToOrderScreen {
useListener("click-refund-order-uid", this._onClickRefundOrderUid);
useListener("update-selected-orderline", this._onUpdateSelectedOrderline);
useListener("do-refund", this._onDoRefund);
numberBuffer.use({
this.numberBuffer = useService("number_buffer");
this.numberBuffer.use({
nonKeyboardInputEvent: "numpad-click-input",
triggerAtInput: "update-selected-orderline",
});
@@ -118,14 +121,14 @@ export class TicketScreen extends IndependentToOrderScreen {
this._state.ui.selectedOrderlineIds[clickedOrder.backendId] = firstLine.id;
}
}
numberBuffer.reset();
this.numberBuffer.reset();
} else {
this._setOrder(clickedOrder);
}
}
_onCreateNewOrder() {
this.env.pos.add_new_order();
this.showScreen("ProductScreen");
this.pos.showScreen("ProductScreen");
}
_selectNextOrder(currentOrder) {
const currentOrderIndex = this._getOrderList().indexOf(currentOrder);
@@ -140,7 +143,7 @@ export class TicketScreen extends IndependentToOrderScreen {
["ProductScreen", "PaymentScreen"].includes(screen.name) &&
order.get_orderlines().length > 0
) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Existing orderlines"),
body: _.str.sprintf(
this.env._t(
@@ -180,7 +183,7 @@ export class TicketScreen extends IndependentToOrderScreen {
_onClickOrderline({ detail: orderline }) {
const order = this.getSelectedSyncedOrder();
this._state.ui.selectedOrderlineIds[order.backendId] = orderline.id;
numberBuffer.reset();
this.numberBuffer.reset();
}
_onClickRefundOrderUid({ detail: orderUid }) {
// Open the refund order.
@@ -193,24 +196,24 @@ export class TicketScreen extends IndependentToOrderScreen {
const buffer = detail.buffer;
const order = this.getSelectedSyncedOrder();
if (!order) {
return numberBuffer.reset();
return this.numberBuffer.reset();
}
const selectedOrderlineId = this.getSelectedOrderlineId();
const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
if (!orderline) {
return numberBuffer.reset();
return this.numberBuffer.reset();
}
const toRefundDetail = this._getToRefundDetail(orderline);
// When already linked to an order, do not modify the to refund quantity.
if (toRefundDetail.destinationOrderUid) {
return numberBuffer.reset();
return this.numberBuffer.reset();
}
const refundableQty = toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty;
if (refundableQty <= 0) {
return numberBuffer.reset();
return this.numberBuffer.reset();
}
if (buffer == null || buffer == "") {
@@ -218,8 +221,8 @@ export class TicketScreen extends IndependentToOrderScreen {
} else {
const quantity = Math.abs(parse.float(buffer));
if (quantity > refundableQty) {
numberBuffer.reset();
this.showPopup(ErrorPopup, {
this.numberBuffer.reset();
this.popup.add(ErrorPopup, {
title: this.env._t("Maximum Exceeded"),
body: _.str.sprintf(
this.env._t(
@@ -3,6 +3,7 @@ import { OfflineErrorPopup } from "./Popups/OfflineErrorPopup";
import { ConfirmPopup } from "./Popups/ConfirmPopup";
import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup";
import { ErrorPopup } from "./Popups/ErrorPopup";
import { useEnv } from "@odoo/owl";
const { onMounted, onPatched, onWillUnmount, useComponent } = owl;
@@ -11,14 +12,16 @@ const { onMounted, onPatched, onWillUnmount, useComponent } = owl;
*
* IMPROVEMENT: This is a terrible hook. There could be a better way to handle
* the error when the order failed to sync.
* FIXME POSREF: move this to the error_handler registry.
*/
export function useErrorHandlers() {
const component = useComponent();
const popup = useEnv().services.popup;
component._handlePushOrderError = async function (error) {
// This error handler receives `error` equivalent to `error.message` of the rpc error.
if (error.message === "Backend Invoice") {
await this.showPopup(ConfirmPopup, {
await popup.add(ConfirmPopup, {
title: this.env._t("Please print the invoice from the backend"),
body:
this.env._t(
@@ -31,10 +34,10 @@ export function useErrorHandlers() {
const body = this.env._t(
"Check the internet connection then try to sync again by clicking on the red wifi button (upper right of the screen)."
);
await this.showPopup(OfflineErrorPopup, { title, body });
await popup.add(OfflineErrorPopup, { title, body });
} else if (error.code === 200) {
// OpenERP Server Errors
await this.showPopup(ErrorTracebackPopup, {
await popup.add(ErrorTracebackPopup, {
title: error.data.message || this.env._t("Server Error"),
body:
error.data.debug ||
@@ -42,7 +45,7 @@ export function useErrorHandlers() {
});
} else if (error.code === 700) {
// Sweden Fiscal module errors
await this.showPopup(ErrorPopup, {
await popup.add(ErrorPopup, {
title: this.env._t("Fiscal data module error"),
body:
error.data.error.status ||
@@ -58,13 +61,13 @@ export function useErrorHandlers() {
} else {
bodyMessage = "Fiscal data module is not on.";
}
await this.showPopup(ErrorPopup, {
await popup.add(ErrorPopup, {
title: this.env._t("Fiscal data module error"),
body: bodyMessage,
});
} else {
// ???
await this.showPopup(ErrorPopup, {
await popup.add(ErrorPopup, {
title: this.env._t("Unknown Error"),
body: this.env._t(
"The order could not be sent to the server due to an unknown error"
+55 -53
View File
@@ -7,7 +7,6 @@ import core from "web.core";
import field_utils from "web.field_utils";
import time from "web.time";
import utils from "web.utils";
import { Gui } from "@point_of_sale/js/Gui";
import { batched, uuidv4 } from "@point_of_sale/js/utils";
import { ErrorPopup } from "./Popups/ErrorPopup";
@@ -145,6 +144,10 @@ export class PosGlobalState extends PosModel {
selectedPartner: null,
selectedCategoryId: null,
});
this.ready = new Promise((resolve) => {
this.markReady = resolve;
});
}
getDefaultSearchDetails() {
return {
@@ -167,6 +170,7 @@ export class PosGlobalState extends PosModel {
await this.load_product_uom_unit();
await this.load_orders();
this.set_start_order();
this.markReady();
}
async load_server_data() {
@@ -340,40 +344,37 @@ export class PosGlobalState extends PosModel {
// reload the list of partner, returns as a promise that resolves if there were
// updated partners, and fails if not
async load_new_partners() {
let search_params = { domain: this.prepare_new_partners_domain() };
const search_params = { domain: this.prepare_new_partners_domain() };
if (this.env.pos.config.limited_partners_loading) {
search_params['order'] = 'write_date desc';
search_params["order"] = "write_date desc";
if (this.env.pos.config.partner_load_background) {
search_params['limit'] = this.env.pos.config.limited_partners_amount || 1;
}
else {
search_params['limit'] = 1;
search_params["limit"] = this.env.pos.config.limited_partners_amount || 1;
} else {
search_params["limit"] = 1;
}
}
const partners = await this.env.services
.rpc(
{
model: 'pos.session',
method: 'get_pos_ui_res_partner_by_params',
args: [[odoo.pos_session_id], search_params],
},
{
timeout: 3000,
shadow: true,
}
)
const partners = await this.env.services.rpc(
{
model: "pos.session",
method: "get_pos_ui_res_partner_by_params",
args: [[odoo.pos_session_id], search_params],
},
{
timeout: 3000,
shadow: true,
}
);
if (this.env.pos.config.partner_load_background) {
this.loadPartnersBackground(
search_params['domain'],
search_params["domain"],
this.env.pos.config.limited_partners_amount || 1,
'write_date desc'
"write_date desc"
);
}
if (this.addPartners(partners)) {
return true
}
else {
return false
return true;
} else {
return false;
}
}
@@ -547,7 +548,7 @@ export class PosGlobalState extends PosModel {
page += 1;
} while (products.length == this.config.limited_products_amount);
}
async loadPartnersBackground(domain=[], offset=0, order=false) {
async loadPartnersBackground(domain = [], offset = 0, order = false) {
// Start at the first page since the first set of loaded partners are not actually in the
// same order as this background loading procedure.
let i = 0;
@@ -1482,7 +1483,7 @@ export class Product extends PosModel {
// product.pricelist.item records are loaded with a search_read
// and were automatically sorted based on their _order by the
// ORM. After that they are added in this order to the pricelists.
get_price(pricelist, quantity, price_extra, recurring=false) {
get_price(pricelist, quantity, price_extra, recurring = false) {
var self = this;
var date = moment();
@@ -1512,12 +1513,13 @@ export class Product extends PosModel {
pricelist_items = _.filter(
self.applicablePricelistItems[pricelist.id],
function (item) {
return (
(!item.categ_id || _.contains(category_ids, item.categ_id[0])) &&
(!item.date_start || moment.utc(item.date_start).isSameOrBefore(date)) &&
(!item.date_end || moment.utc(item.date_end).isSameOrAfter(date))
);
});
return (
(!item.categ_id || _.contains(category_ids, item.categ_id[0])) &&
(!item.date_start || moment.utc(item.date_start).isSameOrBefore(date)) &&
(!item.date_end || moment.utc(item.date_end).isSameOrAfter(date))
);
}
);
}
var price = self.lst_price;
@@ -1650,7 +1652,7 @@ export class Orderline extends PosModel {
var packlotline = pack_lot_lines[i][2];
var pack_lot_line = new Packlotline(
{},
{ json: _.extend({...packlotline}, { order_line: this }) }
{ json: _.extend({ ...packlotline }, { order_line: this }) }
);
this.pack_lot_lines.add(pack_lot_line);
}
@@ -1790,7 +1792,7 @@ export class Orderline extends PosModel {
const maxQtyToRefund =
toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty;
if (quant > 0) {
Gui.showPopup(ErrorPopup, {
this.pos.env.services.popup.add(ErrorPopup, {
title: _t("Positive quantity not allowed"),
body: _t(
"Only a negative quantity is allowed for this refund line. Click on +/- to modify the quantity to be refunded."
@@ -1802,7 +1804,7 @@ export class Orderline extends PosModel {
} else if (-quant <= maxQtyToRefund) {
toRefundDetail.qty = -quant;
} else {
Gui.showPopup(ErrorPopup, {
this.pos.env.services.popup.add(ErrorPopup, {
title: _t("Greater than allowed"),
body: _.str.sprintf(
_t(
@@ -2243,7 +2245,7 @@ export class Orderline extends PosModel {
};
}
display_discount_policy() {
return (this.order.pricelist ? this.order.pricelist.discount_policy : "with_discount" );
return this.order.pricelist ? this.order.pricelist.discount_policy : "with_discount";
}
compute_fixed_price(price) {
var order = this.order;
@@ -2605,8 +2607,8 @@ export class Order extends PosModel {
}
this.set_partner(partner);
this.temporary = false; // FIXME
this.to_invoice = false; // FIXME
this.temporary = false; // FIXME
this.to_invoice = false; // FIXME
this.shippingDate = json.shipping_date;
var orderlines = json.lines;
@@ -2622,10 +2624,7 @@ export class Order extends PosModel {
var paymentlines = json.statement_ids;
for (i = 0; i < paymentlines.length; i++) {
var paymentline = paymentlines[i][2];
var newpaymentline = new Payment(
{},
{ pos: this.pos, order: this, json: paymentline }
);
var newpaymentline = new Payment({}, { pos: this.pos, order: this, json: paymentline });
this.paymentlines.add(newpaymentline);
if (i === paymentlines.length - 1) {
@@ -2686,7 +2685,10 @@ export class Order extends PosModel {
_exportShippingDateForPrinting() {
const shippingDate = new Date(this.shippingDate);
const localeShippingDate = field_utils.format.date(
moment(shippingDate), {}, {timezone: false});
moment(shippingDate),
{},
{ timezone: false }
);
const exportedDate = {
localestring: localeShippingDate,
validationDate: shippingDate,
@@ -3399,7 +3401,10 @@ export class Order extends PosModel {
var rounding_method = this.pos.cash_rounding[0].rounding_method;
var remaining = this.get_total_with_tax() - this.get_total_paid();
var sign = this.get_total_with_tax() > 0 ? 1.0 : -1.0;
if(this.get_total_with_tax() < 0 && remaining > 0 || this.get_total_with_tax() > 0 && remaining < 0) {
if (
(this.get_total_with_tax() < 0 && remaining > 0) ||
(this.get_total_with_tax() > 0 && remaining < 0)
) {
rounding_method = rounding_method.endsWith("UP") ? "DOWN" : rounding_method;
}
@@ -3415,16 +3420,13 @@ export class Order extends PosModel {
Math.abs(this.get_total_with_tax()) < this.pos.cash_rounding[0].rounding
) {
return 0;
} else if(rounding_method === "UP" && rounding_applied < 0 && remaining > 0) {
} else if (rounding_method === "UP" && rounding_applied < 0 && remaining > 0) {
rounding_applied += this.pos.cash_rounding[0].rounding;
}
else if(rounding_method === "UP" && rounding_applied > 0 && remaining < 0) {
} else if (rounding_method === "UP" && rounding_applied > 0 && remaining < 0) {
rounding_applied -= this.pos.cash_rounding[0].rounding;
}
else if(rounding_method === "DOWN" && rounding_applied > 0 && remaining > 0){
} else if (rounding_method === "DOWN" && rounding_applied > 0 && remaining > 0) {
rounding_applied -= this.pos.cash_rounding[0].rounding;
}
else if(rounding_method === "DOWN" && rounding_applied < 0 && remaining < 0){
} else if (rounding_method === "DOWN" && rounding_applied < 0 && remaining < 0) {
rounding_applied += this.pos.cash_rounding[0].rounding;
}
return sign * rounding_applied;
@@ -3587,7 +3589,7 @@ export class Order extends PosModel {
}
/* ---- Ship later --- */
setShippingDate(shippingDate) {
this.shippingDate = shippingDate
this.shippingDate = shippingDate;
}
getShippingDate() {
return this.shippingDate;
@@ -22,6 +22,7 @@ import core from "web.core";
* by overriding the loader_params of the models in the back end
* in the `pos.session` model
*/
// FIXME POSREF convert this to a class.
export const PaymentInterface = core.Class.extend({
init: function (pos, payment_method) {
this.pos = pos;
@@ -3,7 +3,6 @@
import Session from "web.Session";
import core from "web.core";
import { Gui } from "@point_of_sale/js/Gui";
import { ErrorPopup } from "./Popups/ErrorPopup";
var _t = core._t;
@@ -115,7 +114,7 @@ export const PrinterMixin = {
_onIoTActionResult: function (data) {
if (this.pos && (data === false || data.result === false)) {
Gui.showPopup(ErrorPopup, {
this.pos.env.services.popup.add(ErrorPopup, {
title: _t("Connection to the printer failed"),
body: _t("Please check if the printer is still connected."),
});
@@ -124,7 +123,7 @@ export const PrinterMixin = {
_onIoTActionFail: function () {
if (this.pos) {
Gui.showPopup(ErrorPopup, {
this.pos.env.services.popup.add(ErrorPopup, {
title: _t("Connection to IoT Box failed"),
body: _t("Please check if the IoT Box is still connected."),
});
+6
View File
@@ -41,3 +41,9 @@ export function withComputedProperties(obj, sources, descriptor) {
}
return obj;
}
export class Reactive {
constructor() {
return reactive(this);
}
}
@@ -11,10 +11,14 @@
<div class="subwindow">
<div class="subwindow-container">
<div class="subwindow-container-fix screens">
<!-- FIXME POSREF: remove the t-key after owl 2.0.6+ is merged, see https://github.com/odoo/owl/pull/1344 -->
<!-- FIXME POSREF: better error handling in main screens (currently, a crash in owl lifecycle of a main screen blows up the application and the error can't be displayed) -->
<t isShown="!pos.tempScreen" t-component="pos.mainScreen.component"
t-props="pos.mainScreen.props" t-key="pos.mainScreen.name" />
<t t-if="pos.tempScreen" t-component="pos.tempScreen.component"
t-props="pos.tempScreen.props" t-key="pos.tempScreen.name" />
t-props="pos.mainScreen.props" t-key="pos.mainScreen.component.name"/>
<ErrorHandler onError.bind="onTempScreenError">
<t t-if="pos.tempScreen" t-component="pos.tempScreen.component"
t-props="pos.tempScreen.props" t-key="pos.tempScreen.name" />
</ErrorHandler>
</div>
</div>
</div>
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="point_of_sale.PosPopupController" owl="1">
<div class="popups" t-att-hidden="!isShown()">
<t t-foreach="popups" t-as="popup" t-key="popup.props.id">
<div role="dialog" class="modal-dialog" t-att-class="{ oe_hidden: !shouldShow(popup) }">
<t t-component="popup.component" t-props="popup.props" />
</div>
</t>
</div>
</t>
</templates>
@@ -7,7 +7,7 @@
<t t-if="!env.isMobile">
<div class="top-content">
<div class="button back"
t-on-click="() => this.showScreen('ProductScreen')">
t-on-click="() => pos.showScreen('ProductScreen')">
<i class="fa fa-angle-double-left fa-fw"></i>
<span class="back_text">Back</span>
</div>
@@ -108,7 +108,7 @@
t-on-click="() => this.trigger('validate-order')">
<h1>Validate</h1>
</button>
<button class="btn-switchpane secondary" t-on-click="() => this.showScreen('ProductScreen', {mobile_pane: 'left'})">
<button class="btn-switchpane secondary" t-on-click="() => pos.showScreen('ProductScreen', {mobile_pane: 'left'})">
<h1>Review</h1>
</button>
</div>
@@ -1,34 +1,41 @@
/** @odoo-module */
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import makeTestEnvironment from "web.test_env";
import testUtils from "web.test_utils";
import { mount } from "@web/../tests/helpers/utils";
import { LegacyComponent } from "@web/legacy/legacy_component";
import { useService } from "@web/core/utils/hooks";
import { numberBuffer } from "@point_of_sale/app/number_buffer_service";
import { registry } from "@web/core/registry";
const { useState, xml } = owl;
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { useState, xml } from "@odoo/owl";
QUnit.module("unit tests for NumberBuffer", {
before() {},
async beforeEach() {
registry.category("services").add("number_buffer", numberBuffer);
registry.category("services").add("sound", { start: () => ({ play() {} }) });
},
});
QUnit.test("simple fast inputs with capture in between", async function (assert) {
assert.expect(3);
const target = testUtils.prepareTarget();
const env = makeTestEnvironment();
const env = await makeTestEnv();
class Root extends LegacyComponent {
setup() {
this.state = useState({ buffer: "" });
numberBuffer.activate();
numberBuffer.use({
this.numberBuffer = useService("number_buffer");
this.numberBuffer.activate();
this.numberBuffer.use({
nonKeyboardInputEvent: "numpad-click-input",
state: this.state,
});
}
resetBuffer() {
numberBuffer.capture();
numberBuffer.reset();
this.numberBuffer.capture();
this.numberBuffer.reset();
}
onClickOne() {
this.trigger("numpad-click-input", { key: "1" });
@@ -0,0 +1,152 @@
/** @odoo-module */
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import makeTestEnvironment from "web.test_env";
import { click, getFixture, mount, nextTick, triggerEvent } from "@web/../tests/helpers/utils";
import { clearRegistryWithCleanup, makeTestEnv } from "@web/../tests/helpers/mock_env";
import { registry } from "@web/core/registry";
import { popupService } from "@point_of_sale/app/popup/popup_service";
import { MainComponentsContainer } from "@web/core/main_components_container";
import { useService } from "@web/core/utils/hooks";
const { xml } = owl;
// Note that we are creating new popups here to decouple this test from the pos app.
class CustomPopup1 extends AbstractAwaitablePopup {}
CustomPopup1.template = xml/* html */ `
<div class="popup custom-popup-1">
<footer>
<div class="confirm" t-on-click="confirm">
Yes
</div>
<div class="cancel" t-on-click="cancel">
No
</div>
</footer>
</div>
`;
class CustomPopup2 extends AbstractAwaitablePopup {}
CustomPopup2.template = xml/* html */ `
<div class="popup custom-popup-2">
<footer>
<div class="confirm" t-on-click="confirm">
Okay
</div>
</footer>
</div>
`;
class Root extends PosComponent {
static components = { MainComponentsContainer };
setup() {
super.setup();
this.popup = useService("popup");
}
}
Root.template = xml`<MainComponentsContainer/>`;
let env;
QUnit.module("unit tests for PopupContainer", {
async beforeEach() {
const makeService = (obj) => ({
start() {
return obj;
},
});
const legacyEnv = makeTestEnvironment();
registry.category("services").add("popup", popupService);
registry.category("services").add("pos_notification", makeService({ add() {} }));
registry.category("services").add("sound", makeService({ play() {} }));
registry.category("services").add("pos", makeService({ legacyEnv }));
clearRegistryWithCleanup(registry.category("main_components"));
env = await makeTestEnv();
for (const service of ["pos_notification", "sound", "popup"]) {
legacyEnv.services[service] = env.services[service];
}
},
});
QUnit.test("allow multiple popups at the same time", async function (assert) {
const fixture = getFixture();
const root = await mount(Root, fixture, { env });
// Single popup
let popup1Promise = root.popup.add(CustomPopup1, {});
await nextTick();
assert.containsOnce(fixture, ".popup", "popup is open");
await click(fixture.querySelector(".modal-dialog .custom-popup-1 .confirm"));
let result1 = await popup1Promise;
assert.strictEqual(result1.confirmed, true, "popup is confirmed");
assert.containsNone(fixture, ".popup", 0, "popup is closed");
// Multiple popups
popup1Promise = root.popup.add(CustomPopup1, {});
await nextTick();
assert.containsOnce(fixture, ".popup", 1, "first popup is shown");
const popup2Promise = root.popup.add(CustomPopup2, {});
await nextTick();
assert.containsOnce(fixture, ".popup", "only one popup is displayed");
assert.containsOnce(fixture, ".custom-popup-2", "second popup is displayed");
assert.containsNone(fixture, ".custom-popup-1", "first popup is not displayed");
await click(fixture.querySelector(".modal-dialog .custom-popup-2 .confirm"));
assert.containsOnce(fixture, ".popup", "after confirming on popup 2, only 1 should remain");
assert.containsOnce(fixture, ".custom-popup-1", "first popup is displayed");
assert.containsNone(fixture, ".custom-popup-2", "second popup is closed");
await click(fixture.querySelector(".modal-dialog .custom-popup-1 .cancel"));
assert.containsNone(fixture, ".popup", "after cancelling popup 1, no popup should remain.");
result1 = await popup1Promise;
const result2 = await popup2Promise;
assert.strictEqual(result1.confirmed, false, "popup 1 is not confirmed");
assert.strictEqual(result2.confirmed, true, "popup 2 is confirmed");
});
QUnit.test("pressing cancel/confirm key should only close the top popup", async function (assert) {
const fixture = getFixture();
const root = await mount(Root, fixture, { env });
const popup1Promise = root.popup.add(CustomPopup1, {
confirmKey: "Enter",
cancelKey: "Escape",
});
await nextTick();
assert.containsOnce(fixture, ".custom-popup-1", "first popup is open");
const popup2Promise = root.popup.add(CustomPopup2, {
confirmKey: "Enter",
cancelKey: "Escape",
});
await nextTick();
assert.containsNone(fixture, ".custom-popup-1", "first popup no longer displayed");
assert.containsOnce(fixture, ".custom-popup-2", "second popup is open");
await triggerEvent(fixture, ".popup", "keyup", { key: "Escape" });
assert.containsNone(fixture, ".custom-popup-2", "second popup no longer displayed");
assert.containsOnce(fixture, ".custom-popup-1", "first popup is displayed again");
const result2 = await popup2Promise;
assert.strictEqual(
result2.confirmed,
false,
"canceling the popup with escape resolved the promise with confirmed: false"
);
await triggerEvent(fixture, ".popup", "keyup", { key: "Enter" });
assert.containsNone(fixture, ".popup", "no popups remain");
const result1 = await popup1Promise;
assert.strictEqual(
result1.confirmed,
true,
"confirming the popup with enter resolved the promise with confirmed: true"
);
});
@@ -1,181 +0,0 @@
/** @odoo-module */
import { PosPopupController } from "@point_of_sale/js/Popups/PosPopupController";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import makeTestEnvironment from "web.test_env";
import testUtils from "web.test_utils";
import { mount } from "@web/../tests/helpers/utils";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { registry } from "@web/core/registry";
const { EventBus, useSubEnv, xml } = owl;
// Note that we are creating new popups here to decouple this test from the pos app.
class CustomPopup1 extends AbstractAwaitablePopup {}
CustomPopup1.template = xml/* html */ `
<div class="popup custom-popup-1">
<footer>
<div class="confirm" t-on-click="confirm">
Yes
</div>
<div class="cancel" t-on-click="cancel">
No
</div>
</footer>
</div>
`;
class CustomPopup2 extends AbstractAwaitablePopup {}
CustomPopup2.template = xml/* html */ `
<div class="popup custom-popup-2">
<footer>
<div class="confirm" t-on-click="confirm">
Okay
</div>
</footer>
</div>
`;
let env;
QUnit.module("unit tests for PosPopupController", {
async beforeEach() {
const posbus = new EventBus();
const makeService = (obj) => ({
start() {
return obj;
},
});
const legacyEnv = makeTestEnvironment();
registry.category("services").add("pos_notification", makeService({ add() {} }));
registry.category("services").add("sound", makeService({ play() {} }));
registry.category("services").add("pos", makeService({ legacyEnv }));
env = await makeTestEnv();
for (const service of ["pos", "pos_notification", "sound"]) {
legacyEnv.services[service] = env.services[service];
}
env.posbus = posbus;
legacyEnv.posbus = posbus;
},
});
QUnit.test("allow multiple popups at the same time", async function (assert) {
assert.expect(12);
class Root extends PosComponent {
static components = { PosPopupController };
setup() {
super.setup();
useSubEnv({
isDebug: () => false,
});
}
}
Root.template = xml/* html */ `
<div>
<PosPopupController />
</div>
`;
const root = await mount(Root, testUtils.prepareTarget(), { env });
// Check 1 popup
let popup1Promise = root.showPopup(CustomPopup1, {});
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll(".popup").length, 1);
testUtils.dom.click(root.el.querySelector(".modal-dialog .custom-popup-1 .confirm"));
let result1 = await popup1Promise;
assert.strictEqual(result1.confirmed, true);
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll(".popup").length, 0);
// Check multiple popups
popup1Promise = root.showPopup(CustomPopup1, {});
await testUtils.nextTick();
// Check if the first popup is shown.
assert.strictEqual(root.el.querySelectorAll(".popup").length, 1);
const popup2Promise = root.showPopup(CustomPopup2, {});
await testUtils.nextTick();
// Check for the second popup.
assert.strictEqual(root.el.querySelectorAll(".popup").length, 2);
// popup 1 should be hidden
assert.strictEqual(root.el.querySelectorAll(".modal-dialog.oe_hidden").length, 1);
// click confirm on popup 2
testUtils.dom.click(root.el.querySelector(".modal-dialog .custom-popup-2 .confirm"));
await testUtils.nextTick();
// after confirming on popup 2, only 1 should remain.
assert.strictEqual(root.el.querySelectorAll(".popup").length, 1);
assert.strictEqual(root.el.querySelectorAll(".modal-dialog .custom-popup-2").length, 0);
// popup 1 should not be hidden
const customPopup1 = root.el.querySelector(".modal-dialog");
assert.strictEqual(![...customPopup1.classList].includes("oe_hidden"), true);
testUtils.dom.click(root.el.querySelector(".modal-dialog .custom-popup-1 .cancel"));
await testUtils.nextTick();
// after cancelling popup 1, no popup should remain.
assert.strictEqual(root.el.querySelectorAll(".popup").length, 0);
result1 = await popup1Promise;
const result2 = await popup2Promise;
assert.strictEqual(result1.confirmed, false); // false because it's cancelled.
assert.strictEqual(result2.confirmed, true); // true because it's confirmed.
});
QUnit.test("pressing cancel/confirm key should only close the top popup", async function (assert) {
assert.expect(6);
class Root extends PosComponent {
static components = { PosPopupController };
setup() {
super.setup();
useSubEnv({
isDebug: () => false,
});
}
}
Root.template = xml/* html */ `
<div>
<PosPopupController />
</div>
`;
const root = await mount(Root, testUtils.prepareTarget(), { env });
const popup1Promise = root.showPopup(CustomPopup1, {
confirmKey: "Enter",
cancelKey: "Escape",
});
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll(".popup").length, 1);
const popup2Promise = root.showPopup(CustomPopup2, {
confirmKey: "Enter",
cancelKey: "Escape",
});
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll(".popup").length, 2);
// Pressing 'Escape' should cancel the top popup which is the CustomPopup2.
testUtils.dom.triggerEvent(window, "keyup", { key: "Escape" });
await testUtils.nextTick();
// Therefore, the popup2Promise has now resolved with `confirmed` value = false.
const result2 = await popup2Promise;
assert.strictEqual(result2.confirmed, false);
assert.strictEqual(root.el.querySelectorAll(".popup").length, 1);
testUtils.dom.triggerEvent(window, "keyup", { key: "Enter" });
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll(".popup").length, 0);
const result1 = await popup1Promise;
assert.strictEqual(result1.confirmed, true);
});
@@ -3,7 +3,6 @@
import core from "web.core";
import rpc from "web.rpc";
import { PaymentInterface } from "@point_of_sale/js/payment";
import { Gui } from "@point_of_sale/js/Gui";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
var _t = core._t;
@@ -366,7 +365,7 @@ export const PaymentAdyen = PaymentInterface.extend({
if (!title) {
title = _t("Adyen Error");
}
Gui.showPopup(ErrorPopup, {
this.pos.env.services.popup.add(ErrorPopup, {
title: title,
body: msg,
});
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
@@ -11,11 +11,12 @@ export class DiscountButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("click", this.onClick);
}
async onClick() {
var self = this;
const { confirmed, payload } = await this.showPopup(NumberPopup, {
const { confirmed, payload } = await this.popup.add(NumberPopup, {
title: this.env._t("Discount Percentage"),
startingValue: this.env.pos.config.discount_pc,
isInputSelected: true,
@@ -31,7 +32,7 @@ export class DiscountButton extends PosComponent {
var lines = order.get_orderlines();
var product = this.env.pos.db.get_product_by_id(this.env.pos.config.discount_product_id[0]);
if (product === undefined) {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: this.env._t("No discount product found"),
body: this.env._t(
"The discount product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'."
+2 -2
View File
@@ -5,8 +5,8 @@ import { patch } from "@web/core/utils/patch";
import { SelectCashierMixin } from "@pos_hr/js/SelectCashierMixin";
import { useBarcodeReader } from "@point_of_sale/js/custom_hooks";
patch(CashierName.prototype, "pos_hr.CashierName", {
...SelectCashierMixin,
patch(CashierName.prototype, "pos_hr.CashierName SelectCashierMixin", SelectCashierMixin);
patch(CashierName.prototype, "pos_hr.CashierName methods", {
setup() {
this._super(...arguments);
useBarcodeReader({ cashier: this.barcodeCashierAction });
+1 -1
View File
@@ -7,7 +7,7 @@ patch(Chrome.prototype, "pos_hr.Chrome", {
async start() {
await this._super(...arguments);
if (this.env.pos.config.module_pos_hr) {
this.showTempScreen("LoginScreen");
this.pos.showTempScreen("LoginScreen");
}
},
get showCashMoveButton() {
@@ -1,5 +1,6 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
const { useState } = owl;
@@ -10,10 +11,11 @@ export class HeaderLockButton extends PosComponent {
setup() {
super.setup();
this.state = useState({ isUnlockIcon: true, title: "Unlocked" });
this.pos = usePos();
}
async showLoginScreen() {
this.env.pos.reset_cashier();
await this.showTempScreen("LoginScreen");
await this.pos.showTempScreen("LoginScreen");
}
onMouseOver(isMouseOver) {
this.state.isUnlockIcon = !isMouseOver;
+6 -4
View File
@@ -5,16 +5,18 @@ import { SelectCashierMixin } from "@pos_hr/js/SelectCashierMixin";
import { useBarcodeReader } from "@point_of_sale/js/custom_hooks";
import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
import { usePos } from "@point_of_sale/app/pos_hook";
export class LoginScreen extends PosComponent {
static template = "LoginScreen";
}
// FIXME stop this double patch once the mixin is converted to a hook.
// FIXME POSREF stop this double patch once the mixin is converted to a hook.
patch(LoginScreen.prototype, "pos_hr.LoginScreen SelectCashierMixin", SelectCashierMixin);
patch(LoginScreen.prototype, "pos_hr.LoginScreen methods", {
setup() {
this._super(...arguments);
useBarcodeReader({ cashier: this.barcodeCashierAction }, true);
this.pos = usePos();
},
async selectCashier() {
if (await this._super(...arguments)) {
@@ -22,19 +24,19 @@ patch(LoginScreen.prototype, "pos_hr.LoginScreen methods", {
}
},
async barcodeCashierAction(code) {
if (await this._super(code) && this.env.pos.get_cashier().id) {
if ((await this._super(code)) && this.env.pos.get_cashier().id) {
this.back();
}
},
back() {
this.props.resolve({ confirmed: false, payload: false });
this.trigger("close-temp-screen");
this.pos.closeTempScreen();
this.env.pos.hasLoggedIn = true;
this.env.posbus.trigger("start-cash-control");
},
confirm() {
this.props.resolve({ confirmed: true, payload: true });
this.trigger("close-temp-screen");
this.pos.closeTempScreen();
},
get shopName() {
return this.env.pos.config.name;
@@ -4,11 +4,16 @@
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { useService } from "@web/core/utils/hooks";
// FIXME POSREF make this into a hook or something
export const SelectCashierMixin = {
setup() {
this._super(...arguments);
this.popup = useService("popup");
},
async askPin(employee) {
const { confirmed, payload: inputPin } = await this.showPopup(NumberPopup, {
const { confirmed, payload: inputPin } = await this.popup.add(NumberPopup, {
isPassword: true,
title: this.env._t("Password ?"),
startingValue: null,
@@ -21,7 +26,7 @@ export const SelectCashierMixin = {
if (employee.pin === Sha1.hash(inputPin)) {
return employee;
} else {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: this.env._t("Incorrect Password"),
});
return;
@@ -43,7 +48,7 @@ export const SelectCashierMixin = {
isSelected: false,
};
});
let { confirmed, payload: employee } = await this.showPopup(SelectionPopup, {
let { confirmed, payload: employee } = await this.popup.add(SelectionPopup, {
title: this.env._t("Change Cashier"),
list: employeesList,
});
+1 -1
View File
@@ -14,7 +14,7 @@ This module adapts the behavior of the PoS when the pos_hr and pos_restaurant ar
'auto_install': True,
'assets': {
'point_of_sale.assets': [
'pos_hr_restaurant/static/src/js/**/*.js',
'pos_hr_restaurant/static/src/**/*',
],
},
'license': 'LGPL-3',
@@ -0,0 +1,11 @@
/* @odoo-module alias=pos_restaurant_hr.chrome */
import { patch } from "@web/core/utils/patch";
import "@pos_restaurant/app/pos_store";
import { PosStore } from "@point_of_sale/app/pos_store";
patch(PosStore.prototype, "pos_hr_restaurant.PosStore", {
shouldResetIdleTimer() {
return this.tempScreen?.name !== "LoginScreen" && this._super(...arguments);
},
});
@@ -1,11 +0,0 @@
/* @odoo-module alias=pos_restaurant_hr.chrome */
import { Chrome } from "@point_of_sale/js/Chrome";
import { patch } from "@web/core/utils/patch";
patch(Chrome.prototype, "pos_hr_restaurant.Chrome", {
//@override
_shouldResetIdleTimer() {
return this._super() && this.pos.tempScreen?.name !== "LoginScreen";
},
});
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { TextInputPopup } from "@point_of_sale/js/Popups/TextInputPopup";
export class PromoCodeButton extends PosComponent {
@@ -10,11 +10,12 @@ export class PromoCodeButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("click", this.onClick);
}
async onClick() {
let { confirmed, payload: code } = await this.showPopup(TextInputPopup, {
let { confirmed, payload: code } = await this.popup.add(TextInputPopup, {
title: this.env._t("Enter Code"),
startingValue: "",
placeholder: this.env._t("Gift card or Discount code"),
@@ -1,9 +1,8 @@
/** @odoo-module **/
import { Gui } from "@point_of_sale/js/Gui";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
export class RewardButton extends PosComponent {
@@ -11,6 +10,8 @@ export class RewardButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
this.notification = useService("pos_notification");
useListener("click", this.onClick);
}
@@ -70,7 +71,7 @@ export class RewardButton extends PosComponent {
label: this.env.pos.db.get_product_by_id(product_id).display_name,
item: product_id,
}));
const { confirmed, payload: selectedProduct } = await this.showPopup(SelectionPopup, {
const { confirmed, payload: selectedProduct } = await this.popup.add(SelectionPopup, {
title: this.env._t("Please select a product for this reward"),
list: productsList,
});
@@ -93,7 +94,7 @@ export class RewardButton extends PosComponent {
const result = order._applyReward(reward, coupon_id, args);
if (result !== true) {
// Returned an error
Gui.showNotification(result);
this.notification.add(result);
}
order._updateRewards();
return result;
@@ -114,7 +115,7 @@ export class RewardButton extends PosComponent {
label: reward.reward.description,
item: reward,
}));
const { confirmed, payload: selectedReward } = await this.showPopup(SelectionPopup, {
const { confirmed, payload: selectedReward } = await this.popup.add(SelectionPopup, {
title: this.env._t("Please select a reward"),
list: rewardsList,
});
@@ -4,10 +4,16 @@ import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { useService } from "@web/core/utils/hooks";
export class eWalletButton extends PosComponent {
static template = "point_of_sale.eWalletButton";
setup() {
super.setup(...arguments);
this.popup = useService("popup");
}
_getEWalletRewards(order) {
const claimableRewards = order.getClaimableRewards();
const eWalletRewards = claimableRewards.filter(
@@ -28,7 +34,7 @@ export class eWalletButton extends PosComponent {
if (eWalletPrograms.length == 1) {
selectedProgram = eWalletPrograms[0];
} else {
const { confirmed, payload } = await this.showPopup(SelectionPopup, {
const { confirmed, payload } = await this.popup.add(SelectionPopup, {
title: this.env._t("Refund with eWallet"),
list: eWalletPrograms.map((program) => ({
id: program.id,
@@ -55,7 +61,7 @@ export class eWalletButton extends PosComponent {
if (eWalletRewards.length == 1) {
eWalletReward = eWalletRewards[0];
} else {
const { confirmed, payload } = await this.showPopup(SelectionPopup, {
const { confirmed, payload } = await this.popup.add(SelectionPopup, {
title: this.env._t("Use eWallet to pay"),
list: eWalletRewards.map(({ reward, coupon_id }) => ({
id: reward.id,
@@ -75,7 +81,7 @@ export class eWalletButton extends PosComponent {
);
if (result !== true) {
// Returned an error
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Error"),
body: result,
});
+2 -3
View File
@@ -3,7 +3,6 @@
import { Order, Orderline, PosGlobalState } from "@point_of_sale/js/models";
import session from "web.session";
import concurrency from "web.concurrency";
import { Gui } from "@point_of_sale/js/Gui";
import { round_decimals, round_precision } from "web.utils";
import core from "web.core";
import { patch } from "@web/core/utils/patch";
@@ -1627,7 +1626,7 @@ patch(Order.prototype, "pos_loyalty.Order", {
// Allow rejecting a gift card that is not yet paid.
const program = this.pos.program_by_id[payload.program_id];
if (program && program.program_type === "gift_card" && !payload.has_source_order) {
const { confirmed } = await Gui.showPopup(ConfirmPopup, {
const { confirmed } = await this.pos.env.services.popup.add(ConfirmPopup, {
title: _t("Unpaid gift card"),
body: _t(
"This gift card is not linked to any order. Do you really want to apply its reward?"
@@ -1666,7 +1665,7 @@ patch(Order.prototype, "pos_loyalty.Order", {
async activateCode(code) {
const res = await this._activateCode(code);
if (res !== true) {
Gui.showNotification(res);
this.pos.env.services.pos_notification.add(res);
}
},
isProgramsResettable() {
@@ -62,7 +62,7 @@ patch(PaymentScreen.prototype, "pos_loyalty.PaymentScreen", {
);
}
if (!successful) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Error validating rewards"),
body: payload.message,
});
@@ -7,10 +7,12 @@ import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { TextInputPopup } from "@point_of_sale/js/Popups/TextInputPopup";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
import { useService } from "@web/core/utils/hooks";
patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
setup() {
this._super(...arguments);
this.notification = useService("pos_notification");
useBarcodeReader({
coupon: this._onCouponScan,
});
@@ -21,12 +23,12 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
.get_orderlines()
.find((line) => line.getEWalletGiftCardProgramType() === "ewallet");
if (eWalletLine && !order.get_partner()) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Customer needed"),
body: this.env._t("eWallet requires a customer to be selected"),
});
if (confirmed) {
const { confirmed, payload: newPartner } = await this.showTempScreen(
const { confirmed, payload: newPartner } = await this.pos.showTempScreen(
"PartnerListScreen",
{ partner: null }
);
@@ -52,7 +54,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
// If gift card program setting is 'scan_use', ask for the code.
if (this.env.pos.config.gift_card_settings == "scan_use") {
const { confirmed, payload: code } = await this.showPopup(TextInputPopup, {
const { confirmed, payload: code } = await this.popup.add(TextInputPopup, {
title: this.env._t("Generate a Gift Card"),
startingValue: "",
placeholder: this.env._t("Enter the gift card code"),
@@ -78,7 +80,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
// There should be maximum one gift card for a given code.
const giftCard = fetchedGiftCard[0];
if (giftCard && giftCard.source_pos_order_id) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("This gift card has already been sold"),
body: this.env._t(
"You cannot sell a gift card that has already been sold."
@@ -95,7 +97,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
options.giftCardId = giftCard.id;
}
} else {
this.showNotification("Please enter a valid gift card code.");
this.notification.add("Please enter a valid gift card code.");
return false;
}
}
@@ -117,7 +119,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
const linkedPrograms = linkedProgramIds.map((id) => this.env.pos.program_by_id[id]);
let selectedProgram = null;
if (linkedPrograms.length > 1) {
const { confirmed, payload: program } = await this.showPopup(SelectionPopup, {
const { confirmed, payload: program } = await this.popup.add(SelectionPopup, {
title: this.env._t("Select program"),
list: linkedPrograms.map((program) => ({
id: program.id,
@@ -173,7 +175,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
if (selectedLine.eWalletGiftCardProgram) {
// Do not allow negative quantity or price in a gift card or ewallet orderline.
// Refunding gift card or ewallet is not supported.
this.showNotification(
this.notification.add(
this.env._t(
"You cannot set negative quantity or price to gift card or ewallet."
),
@@ -189,7 +191,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", {
(event.detail.key === "Backspace" || event.detail.key === "Delete")
) {
const reward = this.env.pos.reward_by_id[selectedLine.reward_id];
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Deactivating reward"),
body: _.str.sprintf(
this.env._t(
@@ -1,23 +1,27 @@
/** @odoo-module **/
import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScreen";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { useService } from "@web/core/utils/hooks";
import { patch } from "@web/core/utils/patch";
/**
* Prevent refunding ewallet/gift card lines.
*/
patch(TicketScreen.prototype, "pos_loyalty.TicketScreen", {
setup() {
this._super(...arguments);
this.notification = useService("pos_notification");
},
_onUpdateSelectedOrderline() {
const order = this.getSelectedSyncedOrder();
if (!order) {
return numberBuffer.reset();
return this.numberBuffer.reset();
}
const selectedOrderlineId = this.getSelectedOrderlineId();
const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
if (orderline && this._isEWalletGiftCard(orderline)) {
this._showNotAllowedRefundNotification();
return numberBuffer.reset();
return this.numberBuffer.reset();
}
return this._super(...arguments);
},
@@ -31,7 +35,7 @@ patch(TicketScreen.prototype, "pos_loyalty.TicketScreen", {
return this._super(...arguments);
},
_showNotAllowedRefundNotification() {
this.showNotification(
this.notification.add(
this.env._t(
"Refunding a top up or reward product for an eWallet or gift card program is not allowed."
),
@@ -3,7 +3,6 @@
import { _t } from "web.core";
import { patch } from "@web/core/utils/patch";
import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { useBarcodeReader } from "@point_of_sale/js/custom_hooks";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { PaymentTransactionPopup } from "./PaymentTransactionPopup";
@@ -128,7 +127,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", {
* @override
*/
async validateOrder(isForceValidate) {
numberBuffer.capture();
this.numberBuffer.capture();
return this._super(...arguments);
},
/**
@@ -137,7 +136,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", {
* @override
*/
async _sendPaymentRequest({ detail: line }) {
numberBuffer.capture();
this.numberBuffer.capture();
return this._super(...arguments);
},
_get_swipe_pending_line() {
@@ -212,7 +211,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", {
credit_code_transaction(parsed_result, old_deferred, retry_nr) {
var order = this.env.pos.get_order();
if (order.get_due(order.selected_paymentline) < 0) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Refunds not supported"),
body: this.env._t(
"Credit card refunds are not supported. Instead select your credit card payment method, click 'Validate' and refund the original charge manually through the Vantiv backend."
@@ -229,7 +228,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", {
var decodedMagtek = self.env.pos.decodeMagtek(parsed_result.code);
if (!decodedMagtek) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Could not read card"),
body: this.env._t(
"This can be caused by a badly executed swipe or by not having your keyboard layout set to US QWERTY (not US International)."
@@ -354,7 +353,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", {
order.selected_paymentline.mercury_data = response; // used to reverse transactions
order.selected_paymentline.set_credit_card_name();
numberBuffer.reset();
this.numberBuffer.reset();
order.trigger("change", order); // needed so that export_to_JSON gets triggered
self.render();
@@ -421,22 +420,24 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", {
isSelected: false,
item: paymentMethod.item,
}));
this.showPopup(SelectionPopup, {
title: this.env._t("Pay with: "),
list: selectionList,
}).then(({ confirmed, payload: selectedPaymentMethod }) => {
if (confirmed) {
parsed_result.payment_method_id = selectedPaymentMethod;
this.credit_code_transaction(parsed_result);
} else {
this.credit_code_cancel();
}
});
this.popup
.add(SelectionPopup, {
title: this.env._t("Pay with: "),
list: selectionList,
})
.then(({ confirmed, payload: selectedPaymentMethod }) => {
if (confirmed) {
parsed_result.payment_method_id = selectedPaymentMethod;
this.credit_code_transaction(parsed_result);
} else {
this.credit_code_cancel();
}
});
}
},
remove_paymentline_by_ref(line) {
this.env.pos.get_order().remove_paymentline(line);
numberBuffer.reset();
this.numberBuffer.reset();
this.render();
},
do_reversal(line, is_voidsale, old_deferred, retry_nr) {
@@ -446,7 +447,7 @@ patch(PaymentScreen.prototype, "pos_mercury.PaymentScreen", {
// show the transaction popup.
// the transaction deferred is used to update transaction status
this.showPopup(PaymentTransactionPopup, {
this.popup.add(PaymentTransactionPopup, {
transaction: def,
});
@@ -1,9 +0,0 @@
/* @odoo-module */
import { PosPopupController } from "@point_of_sale/js/Popups/PosPopupController";
import { patch } from "@web/core/utils/patch";
import { PaymentTransactionPopup } from "./PaymentTransactionPopup";
patch(PosPopupController, "pos_mercury.PosPopupController", {
components: { ...PosPopupController.components, PaymentTransactionPopup },
});
@@ -13,7 +13,7 @@ patch(ProductScreen.prototype, "pos_mercury.ProductScreen", {
});
},
credit_error_action() {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
body: this.env._t("Go to payment screen to use cards"),
});
},
@@ -1,6 +1,6 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_store";
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class BackToFloorButton extends PosComponent {
@@ -20,6 +20,6 @@ export class BackToFloorButton extends PosComponent {
return this.table != null;
}
backToFloorScreen() {
this.showScreen("FloorScreen", { floor: this.floor });
this.pos.showScreen("FloorScreen", { floor: this.floor });
}
}
@@ -1,8 +1,8 @@
/** @odoo-module */
import { Navbar } from "@point_of_sale/app/navbar/navbar";
import { BackToFloorButton } from "../js/ChromeWidgets/BackToFloorButton";
import { patch } from "@web/core/utils/patch";
import { BackToFloorButton } from "./BackToFloorButton";
patch(Navbar, "pos_restaurant.Navbar", {
components: { ...Navbar.components, BackToFloorButton },
@@ -0,0 +1,24 @@
/* @odoo-module */
import { patch } from "@web/core/utils/patch";
import { popupService } from "@point_of_sale/app/popup/popup_service";
patch(popupService, "pos_restaurant.popupService", {
start() {
return Object.assign(this._super(...arguments), {
closePopupsButError() {
const popups = Object.values(this.popups);
const isErrorPopupOpen = popups.some((popup) =>
// FIXME POSREF: this seems very brittle.
popup.component.name.toLowerCase().includes("error")
);
if (!isErrorPopupOpen) {
for (const popup of popups) {
popup.props.close(false);
}
}
return !isErrorPopupOpen;
},
});
},
});
@@ -0,0 +1,89 @@
/** @odoo-module */
import { patch } from "@web/core/utils/patch";
import { PosStore } from "@point_of_sale/app/pos_store";
import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
import { FloorScreen } from "@pos_restaurant/js/Screens/FloorScreen/FloorScreen";
const NON_IDLE_EVENTS = [
"mousemove",
"mousedown",
"touchstart",
"touchend",
"touchmove",
"click",
"scroll",
"keypress",
];
let IDLE_TIMER_SETTER;
/**
* Go back to the floor screen if the user has been idle for a while.
*/
patch(PosStore.prototype, "pos_restaurant.PosStore", {
/**
* @override
*/
setup() {
this._super(...arguments);
this.globalState.ready.then(() => {
if (this.globalState.config.iface_floorplan) {
this.setActivityListeners();
}
});
},
setActivityListeners() {
IDLE_TIMER_SETTER = this.setIdleTimer.bind(this);
for (const event of NON_IDLE_EVENTS) {
window.addEventListener(event, IDLE_TIMER_SETTER);
}
},
setIdleTimer() {
clearTimeout(this.idleTimer);
if (this.shouldResetIdleTimer()) {
this.idleTimer = setTimeout(() => this.actionAfterIdle(), 60000);
}
},
async actionAfterIdle() {
const isPopupClosed = this.popup.closePopupsButError();
if (isPopupClosed) {
this.closeTempScreen();
const { table } = this.globalState;
const order = this.globalState.get_order();
if (order && order.get_screen_data().name === "ReceiptScreen") {
// When the order is finalized, we can safely remove it from the memory
// We check that it's in ReceiptScreen because we want to keep the order if it's in a tipping state
this.globalState.removeOrder(order);
}
this.showScreen("FloorScreen", { floor: table?.floor });
}
},
shouldResetIdleTimer() {
const stayPaymentScreen =
this.mainScreen.component === PaymentScreen &&
this.globalState.get_order().paymentlines.length > 0;
return (
this.globalState.config.iface_floorplan &&
!stayPaymentScreen &&
this.mainScreen.component !== FloorScreen
);
},
showScreen() {
this._super(...arguments);
this.setIdleTimer();
},
/**
* @override
* FIXME POSREF this is now dead code, check if we still want this
* Before closing pos, we remove the event listeners set on window
* for detecting activities outside FloorScreen.
*/
async _closePos() {
if (IDLE_TIMER_SETTER) {
for (const event of NON_IDLE_EVENTS) {
window.removeEventListener(event, IDLE_TIMER_SETTER);
}
}
return this._super(...arguments);
},
});
+2 -83
View File
@@ -1,34 +1,12 @@
/** @odoo-module */
import { Chrome } from "@point_of_sale/js/Chrome";
import { patch } from "@web/core/utils/patch";
const NON_IDLE_EVENTS =
"mousemove mousedown touchstart touchend touchmove click scroll keypress".split(/\s+/);
let IDLE_TIMER_SETTER;
import { Chrome } from "@point_of_sale/js/Chrome";
patch(Chrome.prototype, "pos_restaurant.Chrome", {
/**
* @override
*/
async start() {
await this._super(...arguments);
if (this.env.pos.config.iface_floorplan) {
this._setActivityListeners();
}
},
/**
* @override
* Do not set `FloorScreen` to the order.
*/
_setScreenData(name) {
if (name === "FloorScreen") {
return;
}
this._super(...arguments);
},
/**
* @override
* FIXME POSREF move to pos store
* `FloorScreen` is the start screen if there are floors.
*/
get startScreen() {
@@ -39,63 +17,4 @@ patch(Chrome.prototype, "pos_restaurant.Chrome", {
return this._super(...arguments);
}
},
_setActivityListeners() {
IDLE_TIMER_SETTER = this._setIdleTimer.bind(this);
for (const event of NON_IDLE_EVENTS) {
window.addEventListener(event, IDLE_TIMER_SETTER);
}
},
_setIdleTimer() {
clearTimeout(this.idleTimer);
if (this._shouldResetIdleTimer()) {
this.idleTimer = setTimeout(() => {
this._actionAfterIdle();
}, 60000);
}
},
async _actionAfterIdle() {
const isPopupClosed = await new Promise((resolve) =>
this.env.posbus.trigger("close-popups-but-error", { resolve })
);
if (isPopupClosed) {
if (this.pos.tempScreen) {
this.trigger("close-temp-screen");
}
const table = this.env.pos.table;
const order = this.env.pos.get_order();
if (order && order.get_screen_data().name === "ReceiptScreen") {
// When the order is finalized, we can safely remove it from the memory
// We check that it's in ReceiptScreen because we want to keep the order if it's in a tipping state
this.env.pos.removeOrder(order);
}
this.showScreen("FloorScreen", { floor: table ? table.floor : null });
}
},
_shouldResetIdleTimer() {
const stayPaymentScreen =
this.pos.mainScreen.name === "PaymentScreen" &&
this.env.pos.get_order().paymentlines.length > 0;
return (
this.env.pos.config.iface_floorplan &&
!stayPaymentScreen &&
this.pos.mainScreen.name !== "FloorScreen"
);
},
__showScreen() {
this._super(...arguments);
this._setIdleTimer();
},
/**
* @override
* Before closing pos, we remove the event listeners set on window
* for detecting activities outside FloorScreen.
*/
async _closePos() {
if (IDLE_TIMER_SETTER) {
for (const event of NON_IDLE_EVENTS) {
window.removeEventListener(event, IDLE_TIMER_SETTER);
}
}
await this._super(...arguments);
},
});
@@ -15,7 +15,7 @@ patch(TicketButton.prototype, "pos_restaurant.TicketButton", {
await this.env.pos._syncAllOrdersFromServer();
} finally {
this.env.pos.setLoadingOrderState(false);
this.showScreen("TicketScreen");
this.pos.showScreen("TicketScreen");
}
} else {
this._super(...arguments);
@@ -1,25 +0,0 @@
/* @odoo-module */
import { PosPopupController } from "@point_of_sale/js/Popups/PosPopupController";
import { useBus } from "@web/core/utils/hooks";
import { patch } from "@web/core/utils/patch";
patch(PosPopupController.prototype, "pos_restaurant.PosPopupController", {
setup() {
this._super(...arguments);
useBus(this.env.posbus, "close-popups-but-error", this._closePopupsButError);
},
_closePopupsButError(event) {
const { resolve } = event.detail;
const isErrorPopupOpen = this.popups.some((popup) =>
popup.name.toLowerCase().includes("error")
);
if (!isErrorPopupOpen) {
for (const popup of this.popups) {
popup.props.resolve(false);
}
this.popups.length = 0; // clearing the array but keep the useState
}
resolve(!isErrorPopupOpen);
},
});
@@ -4,10 +4,10 @@ import { ReceiptScreen } from "@point_of_sale/js/Screens/ReceiptScreen/ReceiptSc
import { registry } from "@web/core/registry";
export class BillScreen extends ReceiptScreen {
static template = "BillScreen";
static template = "pos_restaurant.BillScreen";
confirm() {
this.props.resolve({ confirmed: true, payload: null });
this.trigger("close-temp-screen");
this.pos.closeTempScreen();
}
whenClosing() {
this.confirm();
@@ -11,13 +11,15 @@ import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { EditableTable } from "./EditableTable";
import { EditBar } from "./EditBar";
import { TableWidget } from "./TableWidget";
import { usePos } from "@point_of_sale/app/pos_hook";
import { useService } from "@web/core/utils/hooks";
const { onPatched, onMounted, onWillUnmount, useRef, useState } = owl;
export class FloorScreen extends PosComponent {
static components = { EditableTable, EditBar, TableWidget };
static template = "FloorScreen";
static hideOrderSelector = true;
static storeOnOrder = false;
/**
* @param {Object} props
@@ -25,6 +27,8 @@ export class FloorScreen extends PosComponent {
*/
setup() {
super.setup();
this.pos = usePos();
this.popup = useService("popup");
const floor = this.props.floor ? this.props.floor : this.env.pos.floors[0];
this.state = useState({
selectedFloorId: floor.id,
@@ -205,7 +209,7 @@ export class FloorScreen extends PosComponent {
await this.env.pos.setTable(table);
}
const order = this.env.pos.get_order();
this.showScreen(order.get_screen_data().name);
this.pos.showScreen(order.get_screen_data().name);
}
}
async onSaveTable(table) {
@@ -231,7 +235,7 @@ export class FloorScreen extends PosComponent {
if (!selectedTable) {
return;
}
const { confirmed, payload: newName } = await this.showPopup(TextInputPopup, {
const { confirmed, payload: newName } = await this.popup.add(TextInputPopup, {
startingValue: selectedTable.name,
title: this.env._t("Table Name ?"),
});
@@ -248,7 +252,7 @@ export class FloorScreen extends PosComponent {
if (!selectedTable) {
return;
}
const { confirmed, payload: inputNumber } = await this.showPopup(NumberPopup, {
const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, {
startingValue: selectedTable.seats,
cheap: true,
title: this.env._t("Number of Seats ?"),
@@ -289,7 +293,7 @@ export class FloorScreen extends PosComponent {
if (!this.selectedTable) {
return;
}
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Are you sure ?"),
body: this.env._t("Removing a table cannot be undone"),
});
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { TextAreaPopup } from "@point_of_sale/js/Popups/TextAreaPopup";
export class OrderlineNoteButton extends PosComponent {
@@ -10,6 +10,7 @@ export class OrderlineNoteButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("click", this.onClick);
}
get selectedOrderline() {
@@ -20,7 +21,7 @@ export class OrderlineNoteButton extends PosComponent {
return;
}
const { confirmed, payload: inputNote } = await this.showPopup(TextAreaPopup, {
const { confirmed, payload: inputNote } = await this.popup.add(TextAreaPopup, {
startingValue: this.selectedOrderline.get_note(),
title: this.env._t("Add Internal Note"),
});
@@ -1,5 +1,6 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
@@ -9,6 +10,7 @@ export class PrintBillButton extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
useListener("click", this.onClick);
}
_isDisabled() {
@@ -16,7 +18,7 @@ export class PrintBillButton extends PosComponent {
return order.get_orderlines().length === 0;
}
onClick() {
this.showTempScreen("BillScreen");
this.pos.showTempScreen("BillScreen");
}
}
@@ -1,5 +1,6 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
@@ -9,6 +10,7 @@ export class SplitBillButton extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
useListener("click", this.onClick);
}
_isDisabled() {
@@ -20,7 +22,7 @@ export class SplitBillButton extends PosComponent {
);
}
async onClick() {
this.showScreen("SplitBillScreen");
this.pos.showScreen("SplitBillScreen");
}
}
@@ -3,6 +3,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { useService } from "@web/core/utils/hooks";
/**
* IMPROVEMENT: Perhaps this class is quite complicated for its worth.
@@ -15,6 +16,7 @@ export class SubmitOrderButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
this.clicked = false; //mutex, we don't want to be able to spam the printers
}
async _onClick() {
@@ -27,7 +29,7 @@ export class SubmitOrderButton extends PosComponent {
if (isPrintSuccessful) {
order.updatePrintedResume();
} else {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Printing failed"),
body: this.env._t("Failed in printing the changes in the order"),
});
@@ -2,7 +2,7 @@
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
@@ -11,6 +11,7 @@ export class TableGuestsButton extends PosComponent {
setup() {
super.setup();
this.popup = useService("popup");
useListener("click", this.onClick);
}
get currentOrder() {
@@ -20,7 +21,7 @@ export class TableGuestsButton extends PosComponent {
return this.currentOrder ? this.currentOrder.getCustomerCount() : 0;
}
async onClick() {
const { confirmed, payload: inputNumber } = await this.showPopup(NumberPopup, {
const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, {
startingValue: this.nGuests,
cheap: true,
title: this.env._t("Guests ?"),
@@ -32,7 +33,7 @@ export class TableGuestsButton extends PosComponent {
// Set the maximum number possible for an integer
const max_capacity = 2 ** 31 - 1;
if (guestCount > max_capacity) {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: this.env._t("Blocked action"),
body: _.str.sprintf(
this.env._t("You cannot put a number that exceeds %s "),
@@ -1,5 +1,6 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_hook";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
@@ -9,11 +10,12 @@ export class TransferOrderButton extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
useListener("click", this.onClick);
}
async onClick() {
this.env.pos.setCurrentOrderToTransfer();
this.showScreen("FloorScreen");
this.pos.showScreen("FloorScreen");
}
}
@@ -1,9 +1,10 @@
/** @odoo-module */
import { ReceiptScreen } from "@point_of_sale/js/Screens/ReceiptScreen/ReceiptScreen";
import { usePos } from "@point_of_sale/app/pos_store";
import { usePos } from "@point_of_sale/app/pos_hook";
import { patch } from "@web/core/utils/patch";
import { onWillUnmount } from "@odoo/owl";
import { FloorScreen } from "@pos_restaurant/js/Screens/FloorScreen/FloorScreen";
patch(ReceiptScreen, "pos_restaurant.ReceiptScreen", {
showBackToFloorButton: true,
@@ -15,7 +16,7 @@ patch(ReceiptScreen.prototype, "pos_restaurant.ReceiptScreen", {
this.pos = usePos();
onWillUnmount(() => {
// When leaving the receipt screen to the floor screen the order is paid and can be removed
if (this.pos.mainScreen.name === "FloorScreen") {
if (this.pos.mainScreen.component === FloorScreen) {
this.env.pos.removeOrder(this.currentOrder);
}
});
@@ -6,6 +6,7 @@ import { Order } from "@point_of_sale/js/models";
import { SplitOrderline } from "./SplitOrderline";
import { registry } from "@web/core/registry";
import { usePos } from "@point_of_sale/app/pos_hook";
const { useState, onMounted } = owl;
@@ -15,6 +16,7 @@ export class SplitBillScreen extends PosComponent {
setup() {
super.setup();
this.pos = usePos();
useListener("click-line", this.onClickLine);
this.splitlines = useState(this._initSplitLines(this.env.pos.get_order()));
this.newOrderLines = {};
@@ -46,7 +48,7 @@ export class SplitBillScreen extends PosComponent {
this._updateNewOrder(line);
}
back() {
this.showScreen("ProductScreen");
this.pos.showScreen("ProductScreen");
}
proceed() {
if (_.isEmpty(this.splitlines)) {
@@ -82,7 +84,7 @@ export class SplitBillScreen extends PosComponent {
this.env.pos.orders.add(reactiveNewOrder);
this.env.pos.selectedOrder = reactiveNewOrder;
}
this.showScreen("PaymentScreen");
this.pos.showScreen("PaymentScreen");
}
/**
* @param {models.Order} order
@@ -16,9 +16,9 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", {
const order = this.env.pos.get_order();
if (order) {
const { name: screenName } = order.get_screen_data();
this.showScreen(screenName);
this.pos.showScreen(screenName);
} else {
this.showScreen("FloorScreen");
this.pos.showScreen("FloorScreen");
}
}
},
@@ -101,7 +101,7 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", {
? this.env.pos.getTableOrders(this.env.pos.table.id)
: this.env.pos.orders;
if (orderList.length == 0) {
this.showScreen("FloorScreen");
this.pos.showScreen("FloorScreen");
}
}
},
@@ -135,7 +135,7 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", {
this.env.pos.removeOrder(order);
return true;
} catch {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: "Failed to set tip",
body: `Failed to set tip to ${order.name}. Do you want to proceed on setting the tips of the remaining?`,
});
@@ -6,6 +6,8 @@ import { renderToString } from "@web/core/utils/render";
import { registry } from "@web/core/registry";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { usePos } from "@point_of_sale/app/pos_hook";
import { useService } from "@web/core/utils/hooks";
const { onMounted } = owl;
@@ -14,6 +16,8 @@ export class TipScreen extends PosComponent {
static showBackToFloorButton = true;
setup() {
super.setup();
this.pos = usePos();
this.popup = useService("popup");
this.state = this.currentOrder.uiState.TipScreen;
this._totalAmount = this.currentOrder.get_total_with_tax();
@@ -47,7 +51,7 @@ export class TipScreen extends PosComponent {
const serverId = this.env.pos.validated_orders_name_server_id_map[order.name];
if (!serverId) {
this.showPopup(ErrorPopup, {
this.popup.add(ErrorPopup, {
title: this.env._t("Unsynced order"),
body: this.env._t(
"This order is not yet synced to server. Make sure it is synced then try again."
@@ -67,7 +71,7 @@ export class TipScreen extends PosComponent {
}
if (amount > 0.25 * this.totalAmount) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: "Are you sure?",
body: `${this.env.pos.format_currency(
amount
@@ -104,7 +108,7 @@ export class TipScreen extends PosComponent {
this.env.pos.add_new_order();
}
const { name, props } = this.nextScreen;
this.showScreen(name, props);
this.pos.showScreen(name, props);
}
get nextScreen() {
if (this.env.pos.config.module_pos_restaurant && this.env.pos.config.iface_floorplan) {
@@ -139,7 +143,7 @@ export class TipScreen extends PosComponent {
async _printIoT(receipt) {
const printResult = await this.env.proxy.printer.print_receipt(receipt);
if (!printResult.successful) {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: printResult.message.title,
body: printResult.message.body,
});
@@ -151,7 +155,7 @@ export class TipScreen extends PosComponent {
$(this.el).find(".pos-receipt-container").html(receipt);
window.print();
} catch {
await this.showPopup(ErrorPopup, {
await this.popup.add(ErrorPopup, {
title: this.env._t("Printing is not supported on some browsers"),
body: this.env._t(
"Printing is not supported on some browsers due to no default printing protocol " +
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="BillScreen" owl="1">
<t t-name="pos_restaurant.BillScreen" owl="1">
<div class="receipt-screen screen">
<div class="screen-content">
<div class="top-content">
@@ -6,7 +6,7 @@
<div class="pos-receipt-container"/>
<div class="screen-content">
<div class="top-content">
<span class="button back" t-on-click="() => this.showScreen('FloorScreen')">
<span class="button back" t-on-click="() => this.pos.showScreen('FloorScreen')">
<i class="fa fa-angle-double-left"></i>
<span> </span>
<span>Back</span>
@@ -3,7 +3,7 @@
import { Gui } from "@point_of_sale/js/Gui";
const { EventBus } = owl;
// FIXME POSREF: should probably be a service instead of a singleton.
class SaleOrderFetcher extends EventBus {
constructor() {
super();
@@ -2,10 +2,9 @@
import { sprintf } from "web.utils";
import { parse } from "web.field_utils";
import { useListener } from "@web/core/utils/hooks";
import { useListener, useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { ControlButtonsMixin } from "@point_of_sale/js/ControlButtonsMixin";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { saleOrderFetcher } from "@pos_sale/js/OrderManagementScreen/SaleOrderFetcher";
import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen";
import { orderManagement } from "@point_of_sale/js/PosContext";
@@ -24,10 +23,11 @@ const { onMounted, onWillUnmount, useState } = owl;
export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentToOrderScreen) {
static components = { SaleOrderList, SaleOrderManagementControlPanel };
static template = "SaleOrderManagementScreen";
static hideOrderSelector = true;
setup() {
super.setup();
this.popup = useService("popup");
this.numberBuffer = useService("number_buffer");
useListener("close-screen", this.close);
useListener("click-sale-order", this._onClickSaleOrder);
useListener("next-page", this._onNextPage);
@@ -72,7 +72,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
async _setNumpadMode(event) {
const { mode } = event.detail;
this.numpadMode = mode;
numberBuffer.reset();
this.numberBuffer.reset();
}
_onNextPage() {
saleOrderFetcher.nextPage();
@@ -87,7 +87,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
}
async _onClickSaleOrder(event) {
const clickedOrder = event.detail;
const { confirmed, payload: selectedOption } = await this.showPopup(SelectionPopup, {
const { confirmed, payload: selectedOption } = await this.popup.add(SelectionPopup, {
title: this.env._t("What do you want to do?"),
list: [
{ id: "0", label: this.env._t("Apply a down payment"), item: false },
@@ -116,7 +116,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
this.env._t("There was a problem in loading the %s customer."),
sale_order.partner_id[1]
);
await this.showPopup(ErrorPopup, { title, body });
await this.popup.add(ErrorPopup, { title, body });
}
currentPOSOrder.set_partner(
this.env.pos.db.get_partner_by_id(sale_order.partner_id[0])
@@ -146,7 +146,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
.filter((line) => !this.env.pos.db.get_product_by_id(line.product_id[0]))
.map((line) => line.product_id[0]);
if (product_to_add_in_pos.length) {
const { confirmed } = await this.showPopup(ConfirmPopup, {
const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Products not available in POS"),
body: this.env._t(
"Some of the products in your Sale Order are not available in POS, do you want to import them?"
@@ -202,7 +202,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
// Ask once when `useLoadedLots` is undefined, then reuse it's value on the succeeding lines.
const { confirmed } =
useLoadedLots === undefined
? await this.showPopup(ConfirmPopup, {
? await this.popup.add(ConfirmPopup, {
title: this.env._t("SN/Lots Loading"),
body: this.env._t(
"Do you want to load the SN/Lots linked to the Sales Order?"
@@ -244,7 +244,9 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
this.env.pos.config.down_payment_product_id[0]
);
if (!down_payment_product) {
await this.env.pos._addProducts([this.env.pos.config.down_payment_product_id[0]]);
await this.env.pos._addProducts([
this.env.pos.config.down_payment_product_id[0],
]);
down_payment_product = this.env.pos.db.get_product_by_id(
this.env.pos.config.down_payment_product_id[0]
);
@@ -260,7 +262,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
down_payment = sale_order.amount_total;
}
const { confirmed, payload } = await this.showPopup(NumberPopup, {
const { confirmed, payload } = await this.popup.add(NumberPopup, {
title: sprintf(
this.env._t("Percentage of %s"),
this.env.pos.format_currency(sale_order.amount_total)
@@ -291,7 +293,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo
"It seems that you didn't configure a down payment product in your point of sale.\
You can go to your point of sale configuration to choose one."
);
await this.showPopup(ErrorPopup, { title, body });
await this.popup.add(ErrorPopup, { title, body });
}
}

Some files were not shown because too many files have changed in this diff Show More