[IMP] point_of_sale: allow multiple popups at a time
Previously, only one popup is allowed such that when one is not yet close, but another one is requested, the new one will replace the old. The function that tries to wait for the replaced popup will never resolve resulting to some potential broken state. This commit resolves the said issue which now allows multiple popups at a time. We can use POS instance without demo as an example. Before this commit: The popup that asks whether to load the demo products will be shown but it will be replaced by the cash opening popup. After this commit: The popup that asks whether to load the demo products will be shown, then the cash opening popup will show. When the user is done with the cash opening popup, the demo products confirmation is shown next. closes odoo/odoo#83661 Related: odoo/enterprise#23859 Signed-off-by: Masereel Pierre <pim@odoo.com>
This commit is contained in:
@@ -7,7 +7,6 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
const BarcodeParser = require('barcodes.BarcodeParser');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const PopupControllerMixin = require('point_of_sale.PopupControllerMixin');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const IndependentToOrderScreen = require('point_of_sale.IndependentToOrderScreen');
|
||||
const { identifyError, batched } = require('point_of_sale.utils');
|
||||
@@ -32,7 +31,7 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
/**
|
||||
* Chrome is the root component of the PoS App.
|
||||
*/
|
||||
class Chrome extends PopupControllerMixin(PosComponent) {
|
||||
class Chrome extends PosComponent {
|
||||
setup() {
|
||||
super.setup();
|
||||
useExternalListener(window, 'beforeunload', this._onBeforeUnload);
|
||||
@@ -249,7 +248,7 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
|
||||
openCashControl() {
|
||||
if (this.shouldShowCashControl()) {
|
||||
this.showPopup('CashOpeningPopup', { notEscapable: true });
|
||||
this.showPopup('CashOpeningPopup', { cancelKey: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
odoo.define('point_of_sale.PopupControllerMixin', function(require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
/**
|
||||
* Allows the component declared with this mixin the ability show popup dynamically,
|
||||
* provided the following:
|
||||
* 1. The following element is declared in the template. It is where the Popup will be rendered.
|
||||
* `<t t-if="popup.isShown" t-component="popup.component" t-props="popupProps" t-key="popup.name" />`
|
||||
* 2. The component should trigger `show-popup` event to show the popup and `close-popup` event
|
||||
* to close. In PosComponent, `showPopup` is conveniently declared to satisfy this requirement.
|
||||
* @param {Function} x class definition to mix with during extension
|
||||
*/
|
||||
const PopupControllerMixin = x =>
|
||||
class extends x {
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('show-popup', this.__showPopup);
|
||||
useListener('close-popup', this.__closePopup);
|
||||
|
||||
this.popup = useState({ isShown: false, name: null, component: null });
|
||||
this.popupProps = {}; // We want to avoid making the props to become Proxy!
|
||||
}
|
||||
__showPopup(event) {
|
||||
const { name, props, resolve } = event.detail;
|
||||
const popupConstructor = this.constructor.components[name];
|
||||
if (popupConstructor.dontShow) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.popup.isShown = true;
|
||||
this.popup.name = name;
|
||||
this.popup.component = popupConstructor;
|
||||
this.popupProps = Object.assign({}, props, { resolve });
|
||||
}
|
||||
__closePopup() {
|
||||
this.popup.isShown = false;
|
||||
}
|
||||
};
|
||||
|
||||
return PopupControllerMixin;
|
||||
});
|
||||
@@ -2,8 +2,7 @@ odoo.define('point_of_sale.AbstractAwaitablePopup', function (require) {
|
||||
'use strict';
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
|
||||
const { useExternalListener } = owl;
|
||||
const { useBus } = require('@web/core/utils/hooks');
|
||||
|
||||
/**
|
||||
* Implement this abstract class by extending it like so:
|
||||
@@ -33,22 +32,24 @@ odoo.define('point_of_sale.AbstractAwaitablePopup', function (require) {
|
||||
class AbstractAwaitablePopup extends PosComponent {
|
||||
setup() {
|
||||
super.setup();
|
||||
if (!this.props.notEscapable) {
|
||||
useExternalListener(window, 'keyup', this._cancelAtEscape);
|
||||
if (this.props.confirmKey) {
|
||||
useBus(this.env.posbus, `confirm-popup-${this.props.id}`, this.confirm);
|
||||
}
|
||||
if (this.props.cancelKey) {
|
||||
useBus(this.env.posbus, `cancel-popup-${this.props.id}`, this.cancel);
|
||||
}
|
||||
}
|
||||
async confirm() {
|
||||
this.props.resolve({ confirmed: true, payload: await this.getPayload() });
|
||||
this.trigger('close-popup');
|
||||
this.env.posbus.trigger('close-popup', {
|
||||
popupId: this.props.id,
|
||||
response: { confirmed: true, payload: await this.getPayload() },
|
||||
});
|
||||
}
|
||||
cancel() {
|
||||
this.props.resolve({ confirmed: false, payload: null });
|
||||
this.trigger('close-popup');
|
||||
}
|
||||
_cancelAtEscape(event) {
|
||||
if (event.key === 'Escape') {
|
||||
this.cancel();
|
||||
}
|
||||
this.env.posbus.trigger('close-popup', {
|
||||
popupId: this.props.id,
|
||||
response: { confirmed: false, payload: null },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Override this in the concrete popup implementation to set the
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
odoo.define('point_of_sale.PosPopupController', function(require) {
|
||||
'use strict';
|
||||
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const { useBus } = require('@web/core/utils/hooks');
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class PosPopupController extends PosComponent {
|
||||
setup() {
|
||||
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([]);
|
||||
}
|
||||
_showPopup(event) {
|
||||
let { id, name, props, resolve } = event.detail;
|
||||
props = Object.assign(props || {}, { id, resolve });
|
||||
const component = this.constructor.components[name];
|
||||
if (!component) {
|
||||
throw new Error(`'${name}' is not found. Make sure the file is loaded and the component is properly registered using 'Registries.Component.add'.`);
|
||||
}
|
||||
if (component.dontShow) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.popups.push({
|
||||
name,
|
||||
component,
|
||||
props: this._constructPopupProps(component, props),
|
||||
key: `${name}-${id}`,
|
||||
});
|
||||
}
|
||||
_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) {
|
||||
if (!this.topPopup) 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}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
_constructPopupProps(popupComponent, props) {
|
||||
const defaultProps = popupComponent.defaultProps || {};
|
||||
return Object.assign(
|
||||
{
|
||||
keepBehind: false,
|
||||
cancelKey: 'Escape',
|
||||
confirmKey: 'Enter',
|
||||
},
|
||||
defaultProps,
|
||||
props
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
PosPopupController.template = 'point_of_sale.PosPopupController';
|
||||
Registries.Component.add(PosPopupController);
|
||||
|
||||
return PosPopupController;
|
||||
});
|
||||
@@ -4,6 +4,8 @@ odoo.define('point_of_sale.PosComponent', function (require) {
|
||||
const { LegacyComponent } = require("@web/legacy/legacy_component");
|
||||
const { onRendered } = owl;
|
||||
|
||||
let nextId = 0;
|
||||
|
||||
class PosComponent extends LegacyComponent {
|
||||
setup() {
|
||||
onRendered(() => {
|
||||
@@ -36,7 +38,7 @@ odoo.define('point_of_sale.PosComponent', function (require) {
|
||||
*/
|
||||
showPopup(name, props) {
|
||||
return new Promise((resolve) => {
|
||||
this.trigger('show-popup', { name, props, resolve });
|
||||
this.env.posbus.trigger('show-popup', { name, props, resolve, id: nextId++ });
|
||||
});
|
||||
}
|
||||
showTempScreen(name, props) {
|
||||
|
||||
-1
@@ -14,7 +14,6 @@ odoo.define('point_of_sale.RefundButton', function (require) {
|
||||
_onClick() {
|
||||
const partner = this.env.pos.get_order().get_partner();
|
||||
const searchDetails = partner ? { fieldName: 'PARTNER', searchTerm: partner.name } : {};
|
||||
this.trigger('close-popup');
|
||||
this.showScreen('TicketScreen', {
|
||||
ui: { filter: 'SYNCED', searchDetails },
|
||||
destinationOrder: this.env.pos.get_order(),
|
||||
|
||||
@@ -62,10 +62,7 @@
|
||||
</Transition>
|
||||
|
||||
<!-- Allow popups to be visible at any state of the ui. -->
|
||||
<div t-if="popup.isShown" class="popups">
|
||||
<t t-component="popup.component" t-props="popupProps"
|
||||
t-key="popup.name" />
|
||||
</div>
|
||||
<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"/>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.CashMovePopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup drag-handle">
|
||||
<main class="body">
|
||||
@@ -35,7 +34,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="CashOpeningPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup opening-cash-control">
|
||||
<header class="title drag-handle">
|
||||
OPENING CASH CONTROL
|
||||
@@ -28,6 +27,5 @@
|
||||
onConfirm="(payload) => this.updateCashOpening(payload)"
|
||||
onDiscard.bind="closeDetailsPopup"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ClosePosPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup close-pos-popup">
|
||||
<header class="title">
|
||||
CLOSING CONTROL
|
||||
@@ -105,7 +104,6 @@
|
||||
onConfirm.bind="updateCountedCash"
|
||||
onDiscard.bind="closeDetailsPopup"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ConfirmPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-confirm">
|
||||
<header class="title drag-handle">
|
||||
@@ -21,7 +20,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ControlButtonPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-control-buttons">
|
||||
<div class="control-buttons">
|
||||
<!-- When one of the buttons is clicked, close this popup. -->
|
||||
<div class="control-buttons" t-on-click="cancel">
|
||||
<t t-foreach="controlButtons" t-as="cb" t-key="cb.name">
|
||||
<t t-component="cb.component" t-key="cb.name"/>
|
||||
</t>
|
||||
@@ -17,7 +17,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="EditListPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup popup-text">
|
||||
<header class="title">
|
||||
<t t-esc="props.title" />
|
||||
@@ -22,7 +21,6 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ErrorBarcodePopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-barcode">
|
||||
<header class="title drag-handle">
|
||||
@@ -22,7 +21,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ErrorPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup popup-error">
|
||||
<p class="title">
|
||||
<t t-esc="props.title" />
|
||||
@@ -16,7 +15,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ErrorTracebackPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup popup-error">
|
||||
<header class="title">
|
||||
<t t-esc="props.title" />
|
||||
@@ -30,7 +29,6 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="NumberPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-number" t-att-class="{ 'popup-password': props.isPassword }">
|
||||
<header class="title drag-handle">
|
||||
@@ -58,8 +57,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="OfflineErrorPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-error">
|
||||
<header class="title drag-handle">
|
||||
@@ -19,7 +18,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="OrderImportPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-import">
|
||||
<header class="title drag-handle">
|
||||
@@ -33,7 +32,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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.key">
|
||||
<div role="dialog" class="modal-dialog" t-att-class="{ oe_hidden: !shouldShow(popup) }">
|
||||
<t t-component="popup.component" t-props="popup.props" t-key="popup.key" />
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ProductConfiguratorPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup popup-text popup-med product-configurator-popup">
|
||||
<header class="title">
|
||||
<t t-esc="props.product.display_name" />
|
||||
@@ -26,7 +25,6 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="RadioProductAttribute" owl="1">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="ProductInfoPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup product-info-popup">
|
||||
<header class="title">
|
||||
Product information
|
||||
@@ -136,6 +135,5 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="SelectionPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-selection">
|
||||
<header class="title drag-handle">
|
||||
@@ -23,7 +22,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="TextAreaPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-textarea">
|
||||
<header class="title drag-handle">
|
||||
@@ -19,7 +18,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="TextInputPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup popup-textinput">
|
||||
<header class="title">
|
||||
<t t-esc="props.title" />
|
||||
@@ -22,7 +21,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
odoo.define('point_of_sale.tests.PosPopupController', function(require) {
|
||||
'use strict';
|
||||
|
||||
const PosPopupController = require('point_of_sale.PosPopupController');
|
||||
const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const makeTestEnvironment = require('web.test_env');
|
||||
const testUtils = require('web.test_utils');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { mount } = require('@web/../tests/helpers/utils');
|
||||
|
||||
const { EventBus, useSubEnv, xml } = owl;
|
||||
|
||||
QUnit.module('unit tests for PosPopupController', {
|
||||
before() {
|
||||
Registries.Component.freeze();
|
||||
|
||||
// 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>
|
||||
`;
|
||||
|
||||
PosPopupController.components = { CustomPopup1, CustomPopup2 };
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('allow multiple popups at the same time', async function(assert) {
|
||||
assert.expect(12);
|
||||
|
||||
class Root extends PosComponent {
|
||||
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());
|
||||
|
||||
// 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);
|
||||
|
||||
let 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;
|
||||
let 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 {
|
||||
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());
|
||||
|
||||
let popup1Promise = root.showPopup('CustomPopup1', { confirmKey: 'Enter', cancelKey: 'Escape' });
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
|
||||
|
||||
let 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);
|
||||
});
|
||||
});
|
||||
@@ -14,7 +14,6 @@ odoo.define('pos_coupon.ResetProgramsButton', function (require) {
|
||||
async onClick() {
|
||||
const order = this.env.pos.get_order();
|
||||
order.resetPrograms();
|
||||
this.trigger('close-popup');
|
||||
}
|
||||
}
|
||||
ResetProgramsButton.template = 'ResetProgramsButton';
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="GiftCardPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-textarea">
|
||||
<header class="title drag-handle">
|
||||
@@ -134,7 +133,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="PaymentTransactionPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<div class="popup">
|
||||
<p class="title">
|
||||
<t t-esc="props.title"></t>
|
||||
@@ -16,7 +15,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
-1
@@ -14,7 +14,6 @@ odoo.define('pos_restaurant.PrintBillButton', function(require) {
|
||||
async onClick() {
|
||||
const order = this.env.pos.get_order();
|
||||
if (order.get_orderlines().length > 0) {
|
||||
this.trigger('close-popup');
|
||||
await this.showTempScreen('BillScreen');
|
||||
} else {
|
||||
await this.showPopup('ErrorPopup', {
|
||||
|
||||
-1
@@ -14,7 +14,6 @@ odoo.define('pos_restaurant.SplitBillButton', function(require) {
|
||||
async onClick() {
|
||||
const order = this.env.pos.get_order();
|
||||
if (order.get_orderlines().length > 0) {
|
||||
this.trigger('close-popup');
|
||||
this.showScreen('SplitBillScreen');
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -12,7 +12,6 @@ odoo.define('pos_restaurant.TransferOrderButton', function(require) {
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
async onClick() {
|
||||
this.trigger('close-popup');
|
||||
this.env.pos.transfer_order_to_different_table();
|
||||
this.showScreen('FloorScreen');
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ odoo.define('pos_sale.SetSaleOrderButton', function(require) {
|
||||
args: [[]],
|
||||
kwargs: { context: this.env.session.user_context },
|
||||
});
|
||||
this.trigger('close-popup');
|
||||
this.showScreen('SaleOrderManagementScreen');
|
||||
} catch (error) {
|
||||
if (isConnectionError(error)) {
|
||||
|
||||
Reference in New Issue
Block a user