[REF] point_of_sale, *: move things out of Chrome and store
*: pos_hr_restaurant, pos_restaurant This commit continues the work of simplifying the Chrome component, it moves the popup controller and the notification to their own component and service, and moves the state of the notification, sound and debug widget into their own services. closes odoo/odoo#111553 Related: odoo/enterprise#36573 Signed-off-by: Samuel Degueldre <sad@odoo.com>
This commit is contained in:
@@ -125,6 +125,7 @@
|
||||
'point_of_sale/static/src/js/**/*.js',
|
||||
'web/static/lib/zxing-library/zxing-library.js',
|
||||
'point_of_sale/static/src/xml/**/*.xml',
|
||||
'point_of_sale/static/src/utils.js',
|
||||
'point_of_sale/static/src/app/**/*',
|
||||
],
|
||||
# This bundle contains the code responsible for starting the POS UI.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { reactive } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { DebugWidget } from "./debug_widget";
|
||||
import { withComputedProperties } from "@point_of_sale/utils";
|
||||
|
||||
const debugService = {
|
||||
dependencies: ["pos"],
|
||||
/**
|
||||
* @param {{ debug: string }} env
|
||||
* @param {object} deps
|
||||
* @param {import("@point_of_sale/app/pos_store").PosStore} deps.pos
|
||||
*/
|
||||
start(env, { pos }) {
|
||||
const internalState = reactive({ widgetOpen: true });
|
||||
const state = withComputedProperties(reactive({}), [internalState, pos], {
|
||||
showWidget(internalState, pos) {
|
||||
return env.debug && pos.uiState === "READY" && internalState.widgetOpen;
|
||||
},
|
||||
});
|
||||
registry.category("main_components").add("DebugWidget", {
|
||||
Component: DebugWidget,
|
||||
props: { state },
|
||||
});
|
||||
return {
|
||||
toggleWidget() {
|
||||
internalState.widgetOpen = !internalState.widgetOpen;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("debug", debugService);
|
||||
+3
-6
@@ -3,21 +3,20 @@
|
||||
import { parse } from "web.field_utils";
|
||||
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
|
||||
import { PosComponent } from "@point_of_sale/js/PosComponent";
|
||||
import { usePos } from "@point_of_sale/app/pos_store";
|
||||
import { registry } from "@web/core/registry";
|
||||
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";
|
||||
|
||||
const { onMounted, onWillUnmount, useRef, useState } = owl;
|
||||
import { onMounted, onWillUnmount, useRef, useState } from "@odoo/owl";
|
||||
|
||||
export class DebugWidget extends PosComponent {
|
||||
static components = { Transition, Draggable };
|
||||
static template = "point_of_sale.DebugWidget";
|
||||
setup() {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.debug = useService("debug");
|
||||
this.state = useState({
|
||||
barcodeInput: "",
|
||||
weightInput: "",
|
||||
@@ -162,5 +161,3 @@ export class DebugWidget extends PosComponent {
|
||||
return `"${this.state.buffer}"`;
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("main_components").add("DebugWidget", { Component: DebugWidget });
|
||||
+2
-2
@@ -2,13 +2,13 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.DebugWidget" owl="1">
|
||||
<Transition t-if="env.isDebug() and pos.uiState === 'READY'" visible="pos.debugWidgetIsShown" name="'o-fade'" leaveDuration="200" t-slot-scope="transition">
|
||||
<Transition visible="props.state.showWidget" name="'o-fade'" leaveDuration="200" t-slot-scope="transition">
|
||||
<Draggable limitArea="'.pos'">
|
||||
<div class="debug-widget" t-att-class="transition.className">
|
||||
<header class="drag-handle">
|
||||
<h1>Debug Window</h1>
|
||||
</header>
|
||||
<div class="toggle" t-on-click="() => pos.toggleDebugWidget()" title="Dismiss"
|
||||
<div class="toggle" t-on-click="() => debug.toggleWidget()" title="Dismiss"
|
||||
role="img" aria-label="Dismiss"><i class="fa fa-times" /></div>
|
||||
<div class="content">
|
||||
<p class="category">Electronic Scale</p>
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { PosComponent } from "@point_of_sale/js/PosComponent";
|
||||
import { usePos } from "@point_of_sale/app/pos_store";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { CashierName } from "@point_of_sale/js/ChromeWidgets/CashierName";
|
||||
import { CashMoveButton } from "@point_of_sale/js/ChromeWidgets/CashMoveButton";
|
||||
@@ -30,6 +31,7 @@ export class Navbar extends PosComponent {
|
||||
};
|
||||
setup() {
|
||||
this.pos = usePos();
|
||||
this.debug = useService("debug");
|
||||
}
|
||||
get customerFacingDisplayButtonIsShown() {
|
||||
return this.env.pos.config.iface_customer_facing_display;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="pos-topheader">
|
||||
<div t-if="pos.tempScreen" class="block-top-header" />
|
||||
<div class="pos-branding">
|
||||
<img t-if="!env.isMobile" class="pos-logo" t-on-click="() => pos.toggleDebugWidget()" src="/point_of_sale/static/src/img/logo.png" alt="Logo" />
|
||||
<img t-if="!env.isMobile" class="pos-logo" t-on-click="() => debug.toggleWidget()" src="/point_of_sale/static/src/img/logo.png" alt="Logo" />
|
||||
<CashMoveButton t-if="props.showCashMoveButton" />
|
||||
<TicketButton />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.o_pos_notification_manager {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.o_notification {
|
||||
pointer-events: auto;
|
||||
width: 350px;
|
||||
background-color: #333333AA;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
padding: 16px;
|
||||
margin: .5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.Notification" owl="1">
|
||||
<div class="o_notification" t-att-class="props.className" t-on-click="props.close">
|
||||
<span t-esc="props.message"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="point_of_sale.NotificationContainer" owl="1">
|
||||
<div class="o_pos_notification_manager">
|
||||
<t t-foreach="props.notifications" t-as="notification" t-key="notification">
|
||||
<Transition name="'o-fade'" visible="notification_value.visible" leaveDuration="leaveDuration" t-slot-scope="transition" onLeave="notification_value.delete">
|
||||
<Notification message="notification_value.message" close="notification_value.close" className="transition.className"/>
|
||||
</Transition>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,50 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { reactive, Component } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Transition } from "@web/core/transition";
|
||||
|
||||
const TRANSITION_LEAVE_DURATION = 200; // ms
|
||||
|
||||
class Notification extends Component {
|
||||
static template = "point_of_sale.Notification";
|
||||
static props = { message: String, close: Function, className: String };
|
||||
}
|
||||
|
||||
class NotificationContainer extends Component {
|
||||
static template = "point_of_sale.NotificationContainer";
|
||||
static components = { Transition, Notification };
|
||||
static props = {
|
||||
notifications: Object,
|
||||
};
|
||||
leaveDuration = TRANSITION_LEAVE_DURATION;
|
||||
}
|
||||
// FIXME: probably should use the main notification service from web long term.
|
||||
export const notificationService = {
|
||||
start() {
|
||||
const notifications = reactive({});
|
||||
let notifId = 0;
|
||||
registry.category("main_components").add("PosNotificationContainer", {
|
||||
Component: NotificationContainer,
|
||||
props: { notifications },
|
||||
});
|
||||
return {
|
||||
add(message, duration = 2000) {
|
||||
const id = ++notifId;
|
||||
notifications[id] = {
|
||||
message,
|
||||
visible: true,
|
||||
close() {
|
||||
notifications[id].visible = false;
|
||||
},
|
||||
delete() {
|
||||
delete notifications[id];
|
||||
},
|
||||
};
|
||||
setTimeout(() => notifications?.[id]?.close(), duration);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("pos_notification", notificationService);
|
||||
@@ -1,32 +1,27 @@
|
||||
/** @odoo-module */
|
||||
|
||||
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 { debounce } from "@web/core/utils/timing";
|
||||
import { reactive, useState, toRaw } from "@odoo/owl";
|
||||
import { reactive, useState, markRaw } from "@odoo/owl";
|
||||
|
||||
export class PosStore {
|
||||
/** @type {'LOADING' | 'READY' | 'CLOSING'} */
|
||||
uiState = "LOADING";
|
||||
debugWidgetIsShown = true;
|
||||
hasBigScrollBars = false;
|
||||
notification = {
|
||||
isShown: false,
|
||||
message: "",
|
||||
duration: 2000,
|
||||
};
|
||||
loadingSkipButtonIsShown = false;
|
||||
// not using an arrow function here because we need the correct `this`
|
||||
toggleDebugWidget = debounce(function toggleDebugWidget() {
|
||||
this.debugWidgetIsShown = !toRaw(this).debugWidgetIsShown;
|
||||
}, 100);
|
||||
mainScreen = { name: null, component: null };
|
||||
tempScreen = null;
|
||||
legacyEnv = legacyEnv;
|
||||
constructor() {
|
||||
this.setup();
|
||||
}
|
||||
// to allow other modules to add things to the store.
|
||||
setup() {}
|
||||
// use setup instead of constructor because setup can be patched.
|
||||
setup() {
|
||||
this.globalState = new PosGlobalState({ env: markRaw(legacyEnv) });
|
||||
}
|
||||
}
|
||||
|
||||
export const posService = {
|
||||
@@ -37,6 +32,9 @@ export const posService = {
|
||||
|
||||
registry.category("services").add("pos", posService);
|
||||
|
||||
/**
|
||||
* @returns {PosStore}
|
||||
*/
|
||||
export function usePos() {
|
||||
return useState(useService("pos"));
|
||||
}
|
||||
|
||||
@@ -15,13 +15,10 @@ import { Navbar } from "@point_of_sale/app/navbar/navbar";
|
||||
|
||||
// ChromeAdapter imports
|
||||
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
|
||||
import { PosGlobalState } from "@point_of_sale/js/models";
|
||||
import { configureGui } from "@point_of_sale/js/Gui";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { pos_env as env } from "@point_of_sale/js/pos_env";
|
||||
|
||||
import { Notification } from "./Notification";
|
||||
import { PosPopupController } from "./Popups/PosPopupController";
|
||||
import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup";
|
||||
import { CashOpeningPopup } from "./Popups/CashOpeningPopup";
|
||||
import { ConfirmPopup } from "./Popups/ConfirmPopup";
|
||||
@@ -32,7 +29,6 @@ import {
|
||||
useExternalListener,
|
||||
useSubEnv,
|
||||
reactive,
|
||||
markRaw,
|
||||
onWillUnmount,
|
||||
} from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/pos_store";
|
||||
@@ -43,18 +39,15 @@ import { usePos } from "@point_of_sale/app/pos_store";
|
||||
export class Chrome extends PosComponent {
|
||||
static template = "Chrome"; // FIXME POSREF namespace templates
|
||||
setup() {
|
||||
this.pos = usePos();
|
||||
// BEGIN ChromeAdapter
|
||||
ProductScreen.sortControlButtons();
|
||||
const legacyActionManager = useService("legacy_action_manager");
|
||||
|
||||
// Instantiate PosGlobalState here to ensure that every extension
|
||||
// (or class overloads) is taken into consideration.
|
||||
const pos = new PosGlobalState({ env: markRaw(env) });
|
||||
|
||||
this.batchedCustomerDisplayRender = batched(() => {
|
||||
reactivePos.send_current_order_to_customer_facing_display();
|
||||
});
|
||||
const reactivePos = reactive(pos, this.batchedCustomerDisplayRender);
|
||||
const reactivePos = reactive(this.pos.globalState, this.batchedCustomerDisplayRender);
|
||||
env.pos = reactivePos;
|
||||
env.legacyActionManager = legacyActionManager;
|
||||
|
||||
@@ -63,12 +56,13 @@ export class Chrome extends PosComponent {
|
||||
|
||||
// TODO: Should we continue on exposing posmodel as global variable?
|
||||
// Expose only the reactive version of `pos` when in debug mode.
|
||||
window.posmodel = pos.debug ? reactivePos : pos;
|
||||
window.posmodel = this.pos.globalState.debug ? reactivePos : this.pos.globalState;
|
||||
|
||||
this.wowlEnv = this.env;
|
||||
env.services.pos = this.wowlEnv.services.pos;
|
||||
env.services.sound = this.wowlEnv.services.sound;
|
||||
window.sound = env.services.sound;
|
||||
for (const service of ["pos", "sound", "debug", "pos_notification"]) {
|
||||
env.services[service] = this.wowlEnv.services[service];
|
||||
}
|
||||
|
||||
this.env = env;
|
||||
this.__owl__.childEnv = env;
|
||||
useSubEnv({
|
||||
@@ -94,20 +88,11 @@ export class Chrome extends PosComponent {
|
||||
useListener("close-temp-screen", this.__closeTempScreen);
|
||||
useListener("close-pos", this._closePos);
|
||||
useListener("loading-skip-callback", () => this.env.proxy.stop_searching());
|
||||
const sound = useService("sound");
|
||||
useListener("play-sound", ({ detail: name }) => sound.play(name));
|
||||
useListener("set-sync-status", this._onSetSyncStatus);
|
||||
useListener("show-notification", this._onShowNotification);
|
||||
useListener("close-notification", this._onCloseNotification);
|
||||
useListener("connect-to-proxy", this.connect_to_proxy);
|
||||
useBus(this.env.posbus, "start-cash-control", this.openCashControl);
|
||||
numberBuffer.activate();
|
||||
|
||||
this.state = usePos();
|
||||
|
||||
this.mainScreen = this.state.mainScreen;
|
||||
this.mainScreenProps = {};
|
||||
|
||||
useSubEnv({
|
||||
pos: reactive(
|
||||
this.env.pos,
|
||||
@@ -145,9 +130,9 @@ export class Chrome extends PosComponent {
|
||||
* NOTE: Wait for pos data to be completed before calling this getter.
|
||||
*/
|
||||
get startScreen() {
|
||||
if (this.state.uiState !== "READY") {
|
||||
if (this.pos.uiState !== "READY") {
|
||||
console.warn(
|
||||
`Accessing startScreen of Chrome component before 'state.uiState' to be 'READY' is not recommended.`
|
||||
`Accessing startScreen of Chrome component before 'pos.uiState' to be 'READY' is not recommended.`
|
||||
);
|
||||
}
|
||||
return { name: "ProductScreen" };
|
||||
@@ -184,7 +169,7 @@ export class Chrome extends PosComponent {
|
||||
this.env.pos.config.start_category && this.env.pos.config.iface_start_categ_id
|
||||
? this.env.pos.config.iface_start_categ_id[0]
|
||||
: 0;
|
||||
this.state.uiState = "READY";
|
||||
this.pos.uiState = "READY";
|
||||
this._showStartScreen();
|
||||
setTimeout(() => this._runBackgroundTasks());
|
||||
} catch (error) {
|
||||
@@ -267,7 +252,7 @@ export class Chrome extends PosComponent {
|
||||
connect_to_proxy() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.env.barcode_reader.disconnect_from_proxy();
|
||||
this.state.loadingSkipButtonIsShown = true;
|
||||
this.pos.loadingSkipButtonIsShown = true;
|
||||
this.env.proxy
|
||||
.autoconnect({
|
||||
force_ip: this.env.pos.config.proxy_ip || undefined,
|
||||
@@ -326,21 +311,19 @@ export class Chrome extends PosComponent {
|
||||
}
|
||||
__showTempScreen(event) {
|
||||
const { name, props, resolve } = event.detail;
|
||||
this.state.tempScreen = {
|
||||
this.pos.tempScreen = {
|
||||
name,
|
||||
component: registry.category("pos_screens").get(name),
|
||||
props: { ...props, resolve },
|
||||
};
|
||||
}
|
||||
__closeTempScreen() {
|
||||
this.state.tempScreen = null;
|
||||
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.mainScreen.name = name;
|
||||
this.mainScreen.component = component;
|
||||
this.mainScreenProps = props;
|
||||
this.pos.mainScreen = { name, component, props };
|
||||
|
||||
// 2. Save the screen to the order.
|
||||
// - This screen is shown when the order is selected.
|
||||
@@ -402,8 +385,8 @@ export class Chrome extends PosComponent {
|
||||
});
|
||||
if (confirmed) {
|
||||
// FIXME POSREF setting the location prevents the next render, the loading screen never shows
|
||||
this.state.uiState = "CLOSING";
|
||||
this.state.loadingSkipButtonIsShown = false;
|
||||
this.pos.uiState = "CLOSING";
|
||||
this.pos.loadingSkipButtonIsShown = false;
|
||||
window.location = "/web#action=point_of_sale.action_client_pos_menu";
|
||||
}
|
||||
}
|
||||
@@ -413,14 +396,6 @@ export class Chrome extends PosComponent {
|
||||
this.env.pos.synch.status = status;
|
||||
this.env.pos.synch.pending = pending;
|
||||
}
|
||||
_onShowNotification({ detail: { message, duration } }) {
|
||||
this.state.notification.isShown = true;
|
||||
this.state.notification.message = message;
|
||||
this.state.notification.duration = duration;
|
||||
}
|
||||
_onCloseNotification() {
|
||||
this.state.notification.isShown = false;
|
||||
}
|
||||
/**
|
||||
* Save `env.pos.toRefundLines` in localStorage on beforeunload - closing the
|
||||
* browser, reloading or going to other page.
|
||||
@@ -458,7 +433,7 @@ export class Chrome extends PosComponent {
|
||||
}
|
||||
|
||||
if (this.env.pos.config.iface_big_scrollbars) {
|
||||
this.state.hasBigScrollBars = true;
|
||||
this.pos.hasBigScrollBars = true;
|
||||
}
|
||||
|
||||
this._disableBackspaceBack();
|
||||
@@ -507,8 +482,6 @@ Object.defineProperty(Chrome, "components", {
|
||||
MainComponentsContainer,
|
||||
WithEnv,
|
||||
Navbar,
|
||||
PosPopupController,
|
||||
Notification,
|
||||
},
|
||||
PosComponent.components
|
||||
);
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
import { PosComponent } from "@point_of_sale/js/PosComponent";
|
||||
|
||||
const { onMounted } = owl;
|
||||
|
||||
export class Notification extends PosComponent {
|
||||
static template = "Notification";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener("click", this.closeNotification);
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
this.closeNotification();
|
||||
}, this.props.duration);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
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
|
||||
@@ -29,11 +32,15 @@ export class PosPopupController extends PosComponent {
|
||||
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);
|
||||
owl.useExternalListener(window, "keyup", this._onWindowKeyup);
|
||||
this.popups = owl.useState([]);
|
||||
useExternalListener(window, "keyup", this._onWindowKeyup);
|
||||
this.popups = useState([]);
|
||||
}
|
||||
/**
|
||||
* A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default.
|
||||
@@ -109,3 +116,7 @@ export class PosPopupController extends PosComponent {
|
||||
return this.topPopup === popup || popup.props.keepBehind;
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("main_components").add("PosPopupController", {
|
||||
Component: PosPopupController,
|
||||
});
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
/** @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
|
||||
@@ -42,7 +47,7 @@ export class PosComponent extends LegacyComponent {
|
||||
* @param {String} name 'bell' | 'error'
|
||||
*/
|
||||
playSound(name) {
|
||||
this.trigger("play-sound", name);
|
||||
this.sound.play(name);
|
||||
}
|
||||
/**
|
||||
* Control the SyncNotification component.
|
||||
@@ -52,10 +57,7 @@ export class PosComponent extends LegacyComponent {
|
||||
setSyncStatus(status, pending) {
|
||||
this.trigger("set-sync-status", { status, pending });
|
||||
}
|
||||
showNotification(message, duration = 2000) {
|
||||
this.trigger("show-notification", { message, duration });
|
||||
}
|
||||
closeNotification() {
|
||||
this.trigger("close-notification");
|
||||
showNotification(message, duration) {
|
||||
this.notification.add(message, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3676,24 +3676,6 @@ td {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification
|
||||
*/
|
||||
|
||||
.pos .notification {
|
||||
width: 350px;
|
||||
background-color: #333333AA;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
padding: 16px;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: calc((100% - 350px - 32px)/2);
|
||||
bottom: 5%;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.pos .pos-topheader .status-buttons > div:not(.not-clickable):hover {
|
||||
background: rgba(0, 0, 0, .08);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { reactive } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* Creates a side-effect that runs based on the content of reactive objects.
|
||||
*
|
||||
* @template {object[]} T
|
||||
* @param {(...args: [...T]) => void} cb callback for the effect
|
||||
* @param {[...T]} deps the reactive objects that the effect depends on
|
||||
*/
|
||||
export function effect(cb, deps) {
|
||||
const reactiveDeps = reactive(deps, () => {
|
||||
cb(...reactiveDeps);
|
||||
});
|
||||
cb(...reactiveDeps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds computed properties to a reactive object derived from multiples sources.
|
||||
*
|
||||
* @template {object} T
|
||||
* @template {object[]} U
|
||||
* @template {{[key: string]: (this: T, ...rest: [...U]) => unknown}} V
|
||||
* @param {T} obj the reactive object on which to add the computed
|
||||
* properties
|
||||
* @param {[...U]} sources the reactive objects which are needed to compute
|
||||
* the properties
|
||||
* @param {V} descriptor the object containing methods to compute the
|
||||
* properties
|
||||
* @returns {T & {[key in keyof V]: ReturnType<V[key]>}}
|
||||
*/
|
||||
export function withComputedProperties(obj, sources, descriptor) {
|
||||
for (const [key, compute] of Object.entries(descriptor)) {
|
||||
effect(
|
||||
(obj, sources) => {
|
||||
obj[key] = compute.call(obj, ...sources);
|
||||
},
|
||||
[obj, sources]
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -2,19 +2,19 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="Chrome" owl="1">
|
||||
<div class="pos" t-att-class="{ 'big-scrollbars': state.hasBigScrollBars }">
|
||||
<div class="pos" t-att-class="{ 'big-scrollbars': pos.hasBigScrollBars }">
|
||||
<div class="pos-receipt-print"></div>
|
||||
<t t-if="state.uiState === 'READY'">
|
||||
<t t-if="pos.uiState === 'READY'">
|
||||
<Navbar showCashMoveButton="showCashMoveButton"/>
|
||||
<div class="pos-content">
|
||||
<div class="window">
|
||||
<div class="subwindow">
|
||||
<div class="subwindow-container">
|
||||
<div class="subwindow-container-fix screens">
|
||||
<t isShown="!state.tempScreen" t-component="mainScreen.component"
|
||||
t-props="mainScreenProps" t-key="mainScreen.name" />
|
||||
<t t-if="state.tempScreen" t-component="state.tempScreen.component"
|
||||
t-props="state.tempScreen.props" t-key="state.tempScreen.name" />
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,12 +22,12 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<Transition visible="['LOADING', 'CLOSING'].includes(state.uiState)" name="'o-swing'" leaveDuration="800" t-slot-scope="transition">
|
||||
<Transition visible="['LOADING', 'CLOSING'].includes(pos.uiState)" name="'o-swing'" leaveDuration="800" t-slot-scope="transition">
|
||||
<div class="loader" t-att-class="transition.className">
|
||||
<div class="loader-feedback">
|
||||
<!-- spinner from https://loading.io/css/ -->
|
||||
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
|
||||
<t t-if="state.loadingSkipButtonIsShown">
|
||||
<t t-if="pos.loadingSkipButtonIsShown">
|
||||
<h1 class="message">Connecting to devices</h1>
|
||||
<div class="button skip" t-on-click="() => this.trigger('loading-skip-callback')">
|
||||
Skip
|
||||
@@ -36,14 +36,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Allow popups to be visible at any state of the ui. -->
|
||||
<PosPopupController />
|
||||
|
||||
<Transition name="'o-fade'" visible="state.notification.isShown" leaveDuration="200" t-slot-scope="transition">
|
||||
<Notification message="state.notification.message" duration="state.notification.duration" className="transition.className"/>
|
||||
</Transition>
|
||||
|
||||
</div>
|
||||
<WithEnv env="wowlEnv">
|
||||
<MainComponentsContainer/>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="Notification" owl="1">
|
||||
<div class="notification" t-att-class="props.className">
|
||||
<span>
|
||||
<t t-esc="props.message" />
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -6,6 +6,8 @@ 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;
|
||||
|
||||
@@ -35,29 +37,46 @@ CustomPopup2.template = xml/* html */ `
|
||||
</div>
|
||||
`;
|
||||
|
||||
QUnit.module("unit tests for PosPopupController");
|
||||
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,
|
||||
posbus: new EventBus(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Root.env = makeTestEnvironment();
|
||||
Root.template = xml/* html */ `
|
||||
<div>
|
||||
<PosPopupController />
|
||||
</div>
|
||||
`;
|
||||
|
||||
const root = await mount(Root, testUtils.prepareTarget());
|
||||
const root = await mount(Root, testUtils.prepareTarget(), { env });
|
||||
|
||||
// Check 1 popup
|
||||
let popup1Promise = root.showPopup(CustomPopup1, {});
|
||||
@@ -117,18 +136,16 @@ QUnit.test("pressing cancel/confirm key should only close the top popup", async
|
||||
super.setup();
|
||||
useSubEnv({
|
||||
isDebug: () => false,
|
||||
posbus: new EventBus(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Root.env = makeTestEnvironment();
|
||||
Root.template = xml/* html */ `
|
||||
<div>
|
||||
<PosPopupController />
|
||||
</div>
|
||||
`;
|
||||
|
||||
const root = await mount(Root, testUtils.prepareTarget());
|
||||
const root = await mount(Root, testUtils.prepareTarget(), { env });
|
||||
|
||||
const popup1Promise = root.showPopup(CustomPopup1, {
|
||||
confirmKey: "Enter",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* @odoo-module alias=pos_restaurant_hr.chrome */
|
||||
|
||||
import { Chrome } from '@point_of_sale/js/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.state.tempScreen?.name !== 'LoginScreen';
|
||||
}
|
||||
return this._super() && this.pos.tempScreen?.name !== "LoginScreen";
|
||||
},
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ patch(Chrome.prototype, "pos_restaurant.Chrome", {
|
||||
this.env.posbus.trigger("close-popups-but-error", { resolve })
|
||||
);
|
||||
if (isPopupClosed) {
|
||||
if (this.state.tempScreen) {
|
||||
if (this.pos.tempScreen) {
|
||||
this.trigger("close-temp-screen");
|
||||
}
|
||||
const table = this.env.pos.table;
|
||||
@@ -73,12 +73,12 @@ patch(Chrome.prototype, "pos_restaurant.Chrome", {
|
||||
},
|
||||
_shouldResetIdleTimer() {
|
||||
const stayPaymentScreen =
|
||||
this.mainScreen.name === "PaymentScreen" &&
|
||||
this.pos.mainScreen.name === "PaymentScreen" &&
|
||||
this.env.pos.get_order().paymentlines.length > 0;
|
||||
return (
|
||||
this.env.pos.config.iface_floorplan &&
|
||||
!stayPaymentScreen &&
|
||||
this.mainScreen.name !== "FloorScreen"
|
||||
this.pos.mainScreen.name !== "FloorScreen"
|
||||
);
|
||||
},
|
||||
__showScreen() {
|
||||
|
||||
Reference in New Issue
Block a user