[REF] point_of_sale,pos*: adapt code to owl 2
Owl 2 changelog: https://github.com/odoo/owl/blob/a9f29c4caad4f32d06be1ec4780572825781cd9b/CHANGELOG.md Part-of: odoo/odoo#80156 Co-authored-by: Aaron Bohy <aab@odoo.com> Co-authored-by: Bruno Boi <boi@odoo.com> Co-authored-by: Géry Debongnie <ged@odoo.com> Co-authored-by: Samuel Degueldre <sad@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com> Co-authored-by: Simon Genin (ges) <ges@odoo.com> Co-authored-by: Francois (fge) <fge@odoo.com> Co-authored-by: Michael Mattiello (mcm) <mcm@odoo.com> Co-authored-by: Julien Mougenot <jum@odoo.com> Co-authored-by: luvi <luvi@odoo.com> Co-authored-by: Lucas Perais (lpe) <lpe@odoo.com> Co-authored-by: Jorge Pinna Puissant <jpp@odoo.com>
This commit is contained in:
committed by
Samuel Degueldre
co-authored by
Aaron Bohy
Géry Debongnie
Samuel Degueldre
Mathieu Duckerts-Antoine
Simon Genin
Francois
Michael Mattiello
Julien Mougenot
luvi
Lucas Perais
Jorge Pinna Puissant
parent
56cc3dfab1
commit
0c7910529d
@@ -2817,19 +2817,19 @@ td {
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
.o-fade {
|
||||
transition: opacity .2s;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
.o-fade-enter, .o-fade-leave {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.swing-enter-active, .swing-leave-active {
|
||||
.o-swing {
|
||||
transition: opacity 0.8s;
|
||||
}
|
||||
|
||||
.swing-enter, .swing-leave-to {
|
||||
.o-swing-enter, .o-swing-leave {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,26 +5,14 @@ import { useService } from "@web/core/utils/hooks";
|
||||
import Chrome from "point_of_sale.Chrome";
|
||||
import ProductScreen from "point_of_sale.ProductScreen";
|
||||
import Registries from "point_of_sale.Registries";
|
||||
import { reactive, batched, markRaw } from "@point_of_sale/js/reactivity";
|
||||
import { PosGlobalState } from "point_of_sale.models";
|
||||
import { configureGui } from "point_of_sale.Gui";
|
||||
import { useBus } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import env from "point_of_sale.env";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { batched } from "point_of_sale.utils";
|
||||
|
||||
const { Component, debounce, onMounted, useRef, xml } = owl;
|
||||
|
||||
function setupResponsivePlugin(env) {
|
||||
const isMobile = () => window.innerWidth <= 768;
|
||||
env.isMobile = isMobile();
|
||||
const updateEnv = debounce(() => {
|
||||
if (env.isMobile !== isMobile()) {
|
||||
env.isMobile = !env.isMobile;
|
||||
env.qweb.forceUpdate();
|
||||
}
|
||||
}, 15);
|
||||
window.addEventListener("resize", updateEnv);
|
||||
}
|
||||
const { Component, reactive, markRaw, useExternalListener, useSubEnv, onWillUnmount, xml } = owl;
|
||||
|
||||
export class ChromeAdapter extends Component {
|
||||
setup() {
|
||||
@@ -36,10 +24,10 @@ export class ChromeAdapter extends Component {
|
||||
// (or class overloads) is taken into consideration.
|
||||
const pos = PosGlobalState.create({ env: markRaw(env) });
|
||||
|
||||
const batchedCustomerDisplayRender = batched(() => {
|
||||
this.batchedCustomerDisplayRender = batched(() => {
|
||||
reactivePos.send_current_order_to_customer_facing_display();
|
||||
});
|
||||
const reactivePos = reactive(pos, batchedCustomerDisplayRender);
|
||||
const reactivePos = reactive(pos, this.batchedCustomerDisplayRender);
|
||||
env.pos = reactivePos;
|
||||
env.legacyActionManager = legacyActionManager;
|
||||
|
||||
@@ -51,33 +39,44 @@ export class ChromeAdapter extends Component {
|
||||
window.posmodel = pos.debug ? reactivePos : pos;
|
||||
|
||||
this.env = env;
|
||||
|
||||
useBus(this.env.qweb, "update", () => this.render());
|
||||
setupResponsivePlugin(this.env);
|
||||
|
||||
const chrome = useRef("chrome");
|
||||
onMounted(async () => {
|
||||
// Add the pos error handler when the chrome component is available.
|
||||
registry.category('error_handlers').add(
|
||||
'posErrorHandler',
|
||||
(env, ...noEnvArgs) => {
|
||||
if (chrome.comp) {
|
||||
return chrome.comp.errorHandler(this.env, ...noEnvArgs);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ sequence: 0 }
|
||||
);
|
||||
// Little trick to avoid displaying the block ui during the POS models loading
|
||||
const BlockUiFromRegistry = registry.category("main_components").get("BlockUI");
|
||||
registry.category("main_components").remove("BlockUI");
|
||||
configureGui({ component: chrome.comp });
|
||||
await chrome.comp.start();
|
||||
registry.category("main_components").add("BlockUI", BlockUiFromRegistry);
|
||||
|
||||
// Subscribe to the changes in the models.
|
||||
batchedCustomerDisplayRender();
|
||||
this.__owl__.childEnv = env;
|
||||
useSubEnv({
|
||||
get isMobile() {
|
||||
return window.innerWidth <= 768;
|
||||
},
|
||||
});
|
||||
let currentIsMobile = this.env.isMobile;
|
||||
const updateUI = debounce(() => {
|
||||
if (this.env.isMobile !== currentIsMobile) {
|
||||
currentIsMobile = this.env.isMobile;
|
||||
this.render();
|
||||
}
|
||||
}, 15);
|
||||
useExternalListener(window, "resize", updateUI);
|
||||
onWillUnmount(updateUI.cancel);
|
||||
}
|
||||
|
||||
async configureAndStart(chrome) {
|
||||
// Add the pos error handler when the chrome component is available.
|
||||
registry.category('error_handlers').add(
|
||||
'posErrorHandler',
|
||||
(env, ...noEnvArgs) => {
|
||||
if (chrome) {
|
||||
return chrome.errorHandler(this.env, ...noEnvArgs);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ sequence: 0 }
|
||||
);
|
||||
// Little trick to avoid displaying the block ui during the POS models loading
|
||||
const BlockUiFromRegistry = registry.category("main_components").get("BlockUI");
|
||||
registry.category("main_components").remove("BlockUI");
|
||||
configureGui({ component: chrome });
|
||||
await chrome.start();
|
||||
registry.category("main_components").add("BlockUI", BlockUiFromRegistry);
|
||||
|
||||
// Subscribe to the changes in the models.
|
||||
this.batchedCustomerDisplayRender();
|
||||
}
|
||||
}
|
||||
ChromeAdapter.template = xml`<PosChrome t-ref="chrome" />`;
|
||||
ChromeAdapter.template = xml`<t t-component="PosChrome" setupIsDone.bind="configureAndStart"/>`;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ChromeAdapter } from "@point_of_sale/entry/chrome_adapter";
|
||||
import Registries from "point_of_sale.Registries";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const { Component, Portal, xml } = owl;
|
||||
const { Component, xml } = owl;
|
||||
|
||||
// For consistency's sake, we should trigger"WEB_CLIENT_READY" on the bus when PosApp is mounted
|
||||
// But we can't since mail and some other poll react on that cue, and we don't want those services started
|
||||
@@ -28,7 +28,6 @@ PosApp.template = xml`
|
||||
PosApp.components = { ChromeAdapter };
|
||||
|
||||
function startPosApp() {
|
||||
Registries.Component.add(Portal);
|
||||
Registries.Component.freeze();
|
||||
Registries.Model.freeze();
|
||||
startWebClient(PosApp);
|
||||
|
||||
@@ -2,7 +2,7 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
'use strict';
|
||||
|
||||
const { loadCSS } = require('web.ajax');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const { BarcodeEvents } = require('barcodes.BarcodeEvents');
|
||||
const BarcodeParser = require('barcodes.BarcodeParser');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
@@ -14,16 +14,26 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
const { odooExceptionTitleMap } = require("@web/core/errors/error_dialogs");
|
||||
const { ConnectionLostError, ConnectionAbortedError, RPCError } = require('@web/core/network/rpc_service');
|
||||
const { useBus } = require("@web/core/utils/hooks");
|
||||
const reactivity = require("@point_of_sale/js/reactivity");
|
||||
const { debounce } = require("@web/core/utils/timing");
|
||||
const { Transition } = require("@web/core/transition");
|
||||
|
||||
const { debounce, useExternalListener, useRef, useState } = owl;
|
||||
const {
|
||||
onError,
|
||||
onMounted,
|
||||
onWillDestroy,
|
||||
onWillUnmount,
|
||||
useExternalListener,
|
||||
useRef,
|
||||
useState,
|
||||
useSubEnv,
|
||||
} = owl;
|
||||
|
||||
/**
|
||||
* Chrome is the root component of the PoS App.
|
||||
*/
|
||||
class Chrome extends PopupControllerMixin(PosComponent) {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useExternalListener(window, 'beforeunload', this._onBeforeUnload);
|
||||
useListener('show-main-screen', this.__showScreen);
|
||||
useListener('toggle-debug-widget', debounce(this._toggleDebugWidget, 100));
|
||||
@@ -64,29 +74,31 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
|
||||
this.previous_touch_y_coordinate = -1;
|
||||
|
||||
this.env.pos = reactivity.useState(this.env.pos);
|
||||
}
|
||||
useSubEnv({ pos: useState(this.env.pos) });
|
||||
|
||||
// OVERLOADED METHODS //
|
||||
onMounted(() => {
|
||||
// remove default webclient handlers that induce click delay
|
||||
$(document).off();
|
||||
$(window).off();
|
||||
$('html').off();
|
||||
$('body').off();
|
||||
// The above lines removed the bindings, but we really need them for the barcode
|
||||
BarcodeEvents.start();
|
||||
});
|
||||
|
||||
mounted() {
|
||||
// remove default webclient handlers that induce click delay
|
||||
$(document).off();
|
||||
$(window).off();
|
||||
$('html').off();
|
||||
$('body').off();
|
||||
// The above lines removed the bindings, but we really need them for the barcode
|
||||
BarcodeEvents.start();
|
||||
}
|
||||
willUnmount() {
|
||||
BarcodeEvents.stop();
|
||||
}
|
||||
destroy() {
|
||||
super.destroy(...arguments);
|
||||
this.env.pos.destroy();
|
||||
}
|
||||
catchError(error) {
|
||||
console.error(error);
|
||||
onWillUnmount(() => {
|
||||
BarcodeEvents.stop();
|
||||
});
|
||||
|
||||
onWillDestroy(() => {
|
||||
this.env.pos.destroy();
|
||||
});
|
||||
|
||||
onError((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
this.props.setupIsDone(this);
|
||||
}
|
||||
|
||||
// GETTERS //
|
||||
@@ -362,7 +374,6 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
}
|
||||
_onCloseNotification() {
|
||||
this.state.notification.isShown = false;
|
||||
this.state.notification.message = '';
|
||||
}
|
||||
/**
|
||||
* Save `env.pos.toRefundLines` in localStorage on beforeunload - closing the
|
||||
@@ -535,6 +546,11 @@ odoo.define('point_of_sale.Chrome', function(require) {
|
||||
}
|
||||
}
|
||||
Chrome.template = 'Chrome';
|
||||
Object.defineProperty(Chrome, "components", {
|
||||
get () {
|
||||
return Object.assign({ Transition }, PosComponent.components);
|
||||
}
|
||||
})
|
||||
|
||||
Registries.Component.add(Chrome);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ odoo.define('point_of_sale.CashMoveButton', function (require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { _t } = require('web.core');
|
||||
const { renderToString } = require('@web/core/utils/render');
|
||||
|
||||
const TRANSLATED_CASH_MOVE_TYPE = {
|
||||
in: _t('in'),
|
||||
@@ -30,7 +31,7 @@ odoo.define('point_of_sale.CashMoveButton', function (require) {
|
||||
args: [[this.env.pos.pos_session.id], type, amount, reason, extras],
|
||||
});
|
||||
if (this.env.proxy.printer) {
|
||||
const renderedReceipt = this.env.qweb.renderToString('point_of_sale.CashMoveReceipt', {
|
||||
const renderedReceipt = renderToString('point_of_sale.CashMoveReceipt', {
|
||||
_receipt: this._getReceiptInfo({ ...payload, translatedType, formattedAmount }),
|
||||
});
|
||||
const printResult = await this.env.proxy.printer.print_receipt(renderedReceipt);
|
||||
|
||||
@@ -8,8 +8,8 @@ odoo.define('point_of_sale.ClientScreenButton', function(require) {
|
||||
|
||||
// Formerly ClientScreenWidget
|
||||
class ClientScreenButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.local = this.env.pos.config.iface_customer_facing_display_local && !this.env.pos.config.iface_customer_facing_display_proxy;
|
||||
this.state = useState({ status: this.local ? 'success' : 'failure' });
|
||||
this._start();
|
||||
|
||||
@@ -7,11 +7,11 @@ odoo.define('point_of_sale.DebugWidget', function (require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { useRef, useState } = owl;
|
||||
const { onMounted, onWillUnmount, useRef, useState } = owl;
|
||||
|
||||
class DebugWidget extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({
|
||||
barcodeInput: '',
|
||||
weightInput: '',
|
||||
@@ -44,12 +44,14 @@ odoo.define('point_of_sale.DebugWidget', function (require) {
|
||||
}).bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
mounted() {
|
||||
NumberBuffer.on('buffer-update', this, this._onBufferUpdate);
|
||||
}
|
||||
willUnmount() {
|
||||
NumberBuffer.off('buffer-update', this, this._onBufferUpdate);
|
||||
|
||||
onMounted(() => {
|
||||
NumberBuffer.on('buffer-update', this, this._onBufferUpdate);
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
NumberBuffer.off('buffer-update', this, this._onBufferUpdate);
|
||||
});
|
||||
}
|
||||
toggleWidget() {
|
||||
this.state.isShown = !this.state.isShown;
|
||||
|
||||
@@ -4,12 +4,12 @@ odoo.define('point_of_sale.ProxyStatus', function(require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { useState } = owl;
|
||||
const { onMounted, onWillUnmount, useState } = owl;
|
||||
|
||||
// Previously ProxyStatusWidget
|
||||
class ProxyStatus extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
const initialProxyStatus = this.env.proxy.get('status');
|
||||
this.state = useState({
|
||||
status: initialProxyStatus.status,
|
||||
@@ -17,12 +17,14 @@ odoo.define('point_of_sale.ProxyStatus', function(require) {
|
||||
});
|
||||
this.statuses = ['connected', 'connecting', 'disconnected', 'warning'];
|
||||
this.index = 0;
|
||||
}
|
||||
mounted() {
|
||||
this.env.proxy.on('change:status', this, this._onChangeStatus);
|
||||
}
|
||||
willUnmount() {
|
||||
this.env.proxy.off('change:status', this, this._onChangeStatus);
|
||||
|
||||
onMounted(() => {
|
||||
this.env.proxy.on('change:status', this, this._onChangeStatus);
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
this.env.proxy.off('change:status', this, this._onChangeStatus);
|
||||
});
|
||||
}
|
||||
_onChangeStatus(posProxy, statusChange) {
|
||||
this._setStatus(statusChange.newValue);
|
||||
|
||||
@@ -3,6 +3,7 @@ odoo.define('point_of_sale.SaleDetailsButton', function(require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { renderToString } = require('@web/core/utils/render');
|
||||
|
||||
class SaleDetailsButton extends PosComponent {
|
||||
async onClick() {
|
||||
@@ -14,7 +15,7 @@ odoo.define('point_of_sale.SaleDetailsButton', function(require) {
|
||||
method: 'get_sale_details',
|
||||
args: [false, false, false, [this.env.pos.pos_session.id]],
|
||||
});
|
||||
const report = this.env.qweb.renderToString(
|
||||
const report = renderToString(
|
||||
'SaleDetailsReport',
|
||||
Object.assign({}, saleDetails, {
|
||||
date: new Date().toLocaleString(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
odoo.define('point_of_sale.Gui', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { Component, component } = owl;
|
||||
const { status } = owl;
|
||||
|
||||
/**
|
||||
* This module bridges the data classes (such as those defined in
|
||||
@@ -52,7 +52,7 @@ odoo.define('point_of_sale.Gui', function (require) {
|
||||
get(target, key) {
|
||||
const { component, availableMethods } = target;
|
||||
if (!component) throw new Error(`Call 'configureGui' before using Gui.`);
|
||||
const isMounted = component.__owl__.status === 3 /* mounted */;
|
||||
const isMounted = status(component) === 'mounted';
|
||||
if (availableMethods.has(key) && isMounted) {
|
||||
return component[key].bind(component);
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ odoo.define('point_of_sale.AbstractReceiptScreen', function (require) {
|
||||
* this abstract component.
|
||||
*/
|
||||
class AbstractReceiptScreen extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.orderReceipt = useRef('order-receipt');
|
||||
}
|
||||
async _printReceipt() {
|
||||
if (this.env.proxy.printer) {
|
||||
const printResult = await this.env.proxy.printer.print_receipt(this.orderReceipt.el.outerHTML);
|
||||
const printResult = await this.env.proxy.printer.print_receipt(this.orderReceipt.el.innerHTML);
|
||||
if (printResult.successful) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
odoo.define('point_of_sale.Draggable', function(require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { useExternalListener } = owl;
|
||||
const { onMounted, useExternalListener } = owl;
|
||||
|
||||
/**
|
||||
* Wrap an element or a component with { position: absolute } to make it
|
||||
@@ -30,8 +30,8 @@ odoo.define('point_of_sale.Draggable', function(require) {
|
||||
* @trigger 'drag-end' when dragging ended with payload `{ loc: { top, left } }`
|
||||
*/
|
||||
class Draggable extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.isDragging = false;
|
||||
this.dx = 0;
|
||||
this.dy = 0;
|
||||
@@ -44,33 +44,34 @@ odoo.define('point_of_sale.Draggable', function(require) {
|
||||
|
||||
useListener('mousedown', '.drag-handle', this.startDrag);
|
||||
useListener('touchstart', '.drag-handle', this.startDrag);
|
||||
}
|
||||
mounted() {
|
||||
this.limitArea = this.props.limitArea
|
||||
? document.querySelector(this.props.limitArea)
|
||||
: this.el.offsetParent;
|
||||
this.limitAreaBoundingRect = this.limitArea.getBoundingClientRect();
|
||||
if (this.limitArea === this.el.offsetParent) {
|
||||
this.limitLeft = 0;
|
||||
this.limitTop = 0;
|
||||
this.limitRight = this.limitAreaBoundingRect.width;
|
||||
this.limitBottom = this.limitAreaBoundingRect.height;
|
||||
} else {
|
||||
this.limitLeft = -this.el.offsetParent.offsetLeft;
|
||||
this.limitTop = -this.el.offsetParent.offsetTop;
|
||||
this.limitRight =
|
||||
this.limitAreaBoundingRect.width - this.el.offsetParent.offsetLeft;
|
||||
this.limitBottom =
|
||||
this.limitAreaBoundingRect.height - this.el.offsetParent.offsetTop;
|
||||
}
|
||||
this.limitAreaWidth = this.limitAreaBoundingRect.width;
|
||||
this.limitAreaHeight = this.limitAreaBoundingRect.height;
|
||||
|
||||
// absolutely position the element then remove the transform.
|
||||
const elBoundingRect = this.el.getBoundingClientRect();
|
||||
this.el.style.top = `${elBoundingRect.top}px`;
|
||||
this.el.style.left = `${elBoundingRect.left}px`;
|
||||
this.el.style.transform = 'none';
|
||||
onMounted(() => {
|
||||
this.limitArea = this.props.limitArea
|
||||
? document.querySelector(this.props.limitArea)
|
||||
: this.el.offsetParent;
|
||||
this.limitAreaBoundingRect = this.limitArea.getBoundingClientRect();
|
||||
if (this.limitArea === this.el.offsetParent) {
|
||||
this.limitLeft = 0;
|
||||
this.limitTop = 0;
|
||||
this.limitRight = this.limitAreaBoundingRect.width;
|
||||
this.limitBottom = this.limitAreaBoundingRect.height;
|
||||
} else {
|
||||
this.limitLeft = -this.el.offsetParent.offsetLeft;
|
||||
this.limitTop = -this.el.offsetParent.offsetTop;
|
||||
this.limitRight =
|
||||
this.limitAreaBoundingRect.width - this.el.offsetParent.offsetLeft;
|
||||
this.limitBottom =
|
||||
this.limitAreaBoundingRect.height - this.el.offsetParent.offsetTop;
|
||||
}
|
||||
this.limitAreaWidth = this.limitAreaBoundingRect.width;
|
||||
this.limitAreaHeight = this.limitAreaBoundingRect.height;
|
||||
|
||||
// absolutely position the element then remove the transform.
|
||||
const elBoundingRect = this.el.getBoundingClientRect();
|
||||
this.el.style.top = `${elBoundingRect.top}px`;
|
||||
this.el.style.left = `${elBoundingRect.left}px`;
|
||||
this.el.style.transform = 'none';
|
||||
});
|
||||
}
|
||||
startDrag(event) {
|
||||
let realEvent;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
odoo.define('point_of_sale.NotificationSound', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class NotificationSound extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('ended', () => (this.props.sound.src = null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
odoo.define('point_of_sale.NumberBuffer', function(require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const { parse } = require('web.field_utils');
|
||||
const { BarcodeEvents } = require('barcodes.BarcodeEvents');
|
||||
const { _t } = require('web.core');
|
||||
const { Gui } = require('point_of_sale.Gui');
|
||||
|
||||
const { Component, EventBus, onMounted, onWillUnmount, useExternalListener } = owl;
|
||||
const { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } = owl;
|
||||
const INPUT_KEYS = new Set(
|
||||
['Delete', 'Backspace', '+1', '+2', '+5', '+10', '+20', '+50'].concat('0123456789+-.,'.split(''))
|
||||
);
|
||||
@@ -132,7 +132,7 @@ odoo.define('point_of_sale.NumberBuffer', function(require) {
|
||||
*/
|
||||
use(config) {
|
||||
this.eventsBuffer = [];
|
||||
const currentComponent = Component.current;
|
||||
const currentComponent = useComponent();
|
||||
config = Object.assign(getDefaultConfig(), config);
|
||||
onMounted(() => {
|
||||
this.bufferHolderStack.push({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
odoo.define('point_of_sale.SearchBar', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
@@ -31,8 +31,8 @@ odoo.define('point_of_sale.SearchBar', function (require) {
|
||||
* field of the event.
|
||||
*/
|
||||
class SearchBar extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useExternalListener(window, 'click', this._hideOptions);
|
||||
useListener('click-search-field', this._onClickSearchField);
|
||||
useListener('select-filter', this._onSelectFilter);
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
odoo.define('point_of_sale.Notification', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { onMounted } = owl;
|
||||
|
||||
class Notification extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments)
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.closeNotification);
|
||||
}
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.closeNotification();
|
||||
}, this.props.duration)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
this.closeNotification();
|
||||
}, this.props.duration)
|
||||
});
|
||||
}
|
||||
}
|
||||
Notification.template = 'Notification';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
odoo.define('point_of_sale.PopupControllerMixin', function(require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
@@ -16,8 +16,8 @@ odoo.define('point_of_sale.PopupControllerMixin', function(require) {
|
||||
*/
|
||||
const PopupControllerMixin = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('show-popup', this.__showPopup);
|
||||
useListener('close-popup', this.__closePopup);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ odoo.define('point_of_sale.AbstractAwaitablePopup', function (require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
|
||||
const { useExternalListener, xml } = owl;
|
||||
const { useExternalListener } = owl;
|
||||
|
||||
/**
|
||||
* Implement this abstract class by extending it like so:
|
||||
@@ -31,8 +31,8 @@ odoo.define('point_of_sale.AbstractAwaitablePopup', function (require) {
|
||||
* ```
|
||||
*/
|
||||
class AbstractAwaitablePopup extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
if (!this.props.notEscapable) {
|
||||
useExternalListener(window, 'keyup', this._cancelAtEscape);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ odoo.define('point_of_sale.CashMovePopup', function (require) {
|
||||
|
||||
class CashMovePopup extends AbstractAwaitablePopup {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({
|
||||
inputType: '', // '' | 'in' | 'out'
|
||||
inputAmount: '',
|
||||
|
||||
@@ -4,27 +4,25 @@ odoo.define('point_of_sale.CashOpeningPopup', function(require) {
|
||||
const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { useRef, useState } = owl;
|
||||
const { useState } = owl;
|
||||
|
||||
class CashOpeningPopup extends AbstractAwaitablePopup {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.manualInputCashCount = null;
|
||||
this.state = useState({
|
||||
notes: "",
|
||||
openingCash: this.env.pos.bank_statement.balance_start || 0,
|
||||
displayMoneyDetailsPopup: false,
|
||||
});
|
||||
this.moneyDetailsRef = useRef('moneyDetails');
|
||||
}
|
||||
openDetailsPopup() {
|
||||
if (this.moneyDetailsRef.comp.isClosed()){
|
||||
this.moneyDetailsRef.comp.openPopup();
|
||||
this.state.openingCash = 0;
|
||||
this.state.notes = "";
|
||||
if (this.manualInputCashCount) {
|
||||
this.moneyDetailsRef.comp.reset();
|
||||
}
|
||||
}
|
||||
this.state.openingCash = 0;
|
||||
this.state.notes = "";
|
||||
this.state.displayMoneyDetailsPopup = true;
|
||||
}
|
||||
closeDetailsPopup() {
|
||||
this.state.displayMoneyDetailsPopup = false;
|
||||
}
|
||||
startSession() {
|
||||
this.env.pos.bank_statement.balance_start = this.state.openingCash;
|
||||
@@ -36,13 +34,13 @@ odoo.define('point_of_sale.CashOpeningPopup', function(require) {
|
||||
});
|
||||
this.cancel(); // close popup
|
||||
}
|
||||
updateCashOpening(event) {
|
||||
const { total, moneyDetailsNotes } = event.detail;
|
||||
updateCashOpening({ total, moneyDetailsNotes }) {
|
||||
this.state.openingCash = total;
|
||||
if (moneyDetailsNotes) {
|
||||
this.state.notes = moneyDetailsNotes;
|
||||
}
|
||||
this.manualInputCashCount = false;
|
||||
this.closeDetailsPopup();
|
||||
}
|
||||
handleInputChange() {
|
||||
this.manualInputCashCount = true;
|
||||
|
||||
@@ -6,22 +6,25 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) {
|
||||
const { identifyError } = require('point_of_sale.utils');
|
||||
const { ConnectionLostError, ConnectionAbortedError} = require('@web/core/network/rpc_service')
|
||||
|
||||
const { useRef, useState } = owl;
|
||||
const { onWillStart, onMounted, useState } = owl;
|
||||
|
||||
/**
|
||||
* This popup needs to be self-dependent because it needs to be called from different place.
|
||||
*/
|
||||
class ClosePosPopup extends AbstractAwaitablePopup {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.manualInputCashCount = false;
|
||||
this.cashControl = this.env.pos.config.cash_control;
|
||||
this.moneyDetailsRef = useRef('moneyDetails');
|
||||
this.closeSessionClicked = false;
|
||||
this.moneyDetails = null;
|
||||
this.state = useState({});
|
||||
this.state = useState({
|
||||
displayMoneyDetailsPopup: false,
|
||||
});
|
||||
onWillStart(this.onWillStart);
|
||||
onMounted(this.onMounted);
|
||||
}
|
||||
async willStart() {
|
||||
async onWillStart() {
|
||||
try {
|
||||
const closingData = await this.rpc({
|
||||
model: 'pos.session',
|
||||
@@ -57,7 +60,7 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) {
|
||||
/*
|
||||
* Since this popup need to be self dependent, in case of an error, the popup need to be closed on its own.
|
||||
*/
|
||||
mounted() {
|
||||
onMounted() {
|
||||
if (this.error) {
|
||||
this.cancel();
|
||||
if (identifyError(this.error) instanceof ConnectionLostError) {
|
||||
@@ -71,15 +74,13 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) {
|
||||
}
|
||||
}
|
||||
openDetailsPopup() {
|
||||
if (this.moneyDetailsRef.comp.isClosed()){
|
||||
this.moneyDetailsRef.comp.openPopup();
|
||||
this.state.payments[this.defaultCashDetails.id].counted = 0;
|
||||
this.state.payments[this.defaultCashDetails.id].difference = -this.defaultCashDetails.amount;
|
||||
this.state.notes = '';
|
||||
if (this.manualInputCashCount) {
|
||||
this.moneyDetailsRef.comp.reset();
|
||||
}
|
||||
}
|
||||
this.state.payments[this.defaultCashDetails.id].counted = 0;
|
||||
this.state.payments[this.defaultCashDetails.id].difference = -this.defaultCashDetails.amount;
|
||||
this.state.notes = "";
|
||||
this.state.displayMoneyDetailsPopup = true;
|
||||
}
|
||||
closeDetailsPopup() {
|
||||
this.state.displayMoneyDetailsPopup = false;
|
||||
}
|
||||
handleInputChange(paymentId) {
|
||||
let expectedAmount;
|
||||
@@ -94,8 +95,7 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) {
|
||||
this.env.pos.round_decimals_currency(this.state.payments[paymentId].counted - expectedAmount);
|
||||
this.state.acceptClosing = false;
|
||||
}
|
||||
updateCountedCash(event) {
|
||||
const { total, moneyDetailsNotes, moneyDetails } = event.detail;
|
||||
updateCountedCash({ total, moneyDetailsNotes, moneyDetails }) {
|
||||
this.state.payments[this.defaultCashDetails.id].counted = total;
|
||||
this.state.payments[this.defaultCashDetails.id].difference =
|
||||
this.env.pos.round_decimals_currency(this.state.payments[[this.defaultCashDetails.id]].counted - this.defaultCashDetails.amount);
|
||||
@@ -105,6 +105,7 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) {
|
||||
this.manualInputCashCount = false;
|
||||
this.moneyDetails = moneyDetails;
|
||||
this.state.acceptClosing = false;
|
||||
this.closeDetailsPopup();
|
||||
}
|
||||
hasDifference() {
|
||||
return Object.entries(this.state.payments).find(pm => pm[1].difference != 0);
|
||||
|
||||
@@ -10,8 +10,8 @@ odoo.define('point_of_sale.ControlButtonPopup', function(require) {
|
||||
* @param {Object} props
|
||||
* @param {string} props.startingValue
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.controlButtons = this.props.controlButtons;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ odoo.define('point_of_sale.EditListPopup', function(require) {
|
||||
* @param {Array} [props.array=[]] the array of { id, text } to be edited or an array of strings
|
||||
* @param {Boolean} [props.isSingleItem=false] true if only allowed to edit single item (the first item)
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this._id = 0;
|
||||
this.state = useState({ array: this._initialize(this.props.array) });
|
||||
useAutoFocusToLast();
|
||||
|
||||
@@ -7,7 +7,11 @@ odoo.define('point_of_sale.ErrorPopup', function(require) {
|
||||
|
||||
// formerly ErrorPopupWidget
|
||||
class ErrorPopup extends AbstractAwaitablePopup {
|
||||
mounted() {
|
||||
setup() {
|
||||
super.setup();
|
||||
owl.onMounted(this.onMounted);
|
||||
}
|
||||
onMounted() {
|
||||
this.playSound('error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,16 @@ odoo.define('point_of_sale.MoneyDetailsPopup', function(require) {
|
||||
* and hide it with some css. The confirm button will just trigger an event to the parent.
|
||||
*/
|
||||
class MoneyDetailsPopup extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.currency = this.env.pos.currency;
|
||||
this.state = useState({
|
||||
moneyDetails: Object.fromEntries(this.env.pos.bills.map(bill => ([bill.value, 0]))),
|
||||
total: 0,
|
||||
});
|
||||
if (this.props.manualInputCashCount) {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
get firstHalfMoneyDetails() {
|
||||
const moneyDetailsKeys = Object.keys(this.state.moneyDetails).sort((a, b) => a - b);
|
||||
@@ -28,19 +31,10 @@ odoo.define('point_of_sale.MoneyDetailsPopup', function(require) {
|
||||
const moneyDetailsKeys = Object.keys(this.state.moneyDetails).sort((a, b) => a - b);
|
||||
return moneyDetailsKeys.slice(moneyDetailsKeys.length/2, moneyDetailsKeys.length);
|
||||
}
|
||||
isClosed() {
|
||||
return this.el.classList.contains('invisible')
|
||||
}
|
||||
openPopup() {
|
||||
this.el.classList.remove('invisible');
|
||||
}
|
||||
updateMoneyDetailsAmount() {
|
||||
let total = Object.entries(this.state.moneyDetails).reduce((total, money) => total + money[0] * money[1], 0);
|
||||
this.state.total = this.env.pos.round_decimals_currency(total);
|
||||
}
|
||||
_closePopup() {
|
||||
this.el.classList.add('invisible');
|
||||
}
|
||||
confirm() {
|
||||
let moneyDetailsNotes = this.state.total ? 'Money details: \n' : null;
|
||||
this.env.pos.bills.forEach(bill => {
|
||||
@@ -48,9 +42,8 @@ odoo.define('point_of_sale.MoneyDetailsPopup', function(require) {
|
||||
moneyDetailsNotes += ` - ${this.state.moneyDetails[bill.value]} x ${this.env.pos.format_currency(bill.value)}\n`;
|
||||
}
|
||||
})
|
||||
const payload = { total: this.state.total, moneyDetailsNotes, moneyDetails: { ...this.state.moneyDetails }};
|
||||
this.trigger('money-details-validated', payload);
|
||||
this._closePopup();
|
||||
const payload = { total: this.state.total, moneyDetailsNotes, moneyDetails: { ...this.state.moneyDetails } };
|
||||
this.props.onConfirm(payload);
|
||||
}
|
||||
reset() {
|
||||
for (let key in this.state.moneyDetails) { this.state.moneyDetails[key] = 0 }
|
||||
@@ -58,7 +51,7 @@ odoo.define('point_of_sale.MoneyDetailsPopup', function(require) {
|
||||
}
|
||||
discard() {
|
||||
this.reset();
|
||||
this._closePopup();
|
||||
this.props.onDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ odoo.define('point_of_sale.NumberPopup', function(require) {
|
||||
|
||||
const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { useState } = owl;
|
||||
@@ -22,8 +22,8 @@ odoo.define('point_of_sale.NumberPopup', function(require) {
|
||||
* @confirmed {Boolean}
|
||||
* @payload {String}
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('accept-input', this.confirm);
|
||||
useListener('close-this-popup', this.cancel);
|
||||
let startingBuffer = '';
|
||||
|
||||
@@ -8,8 +8,8 @@ odoo.define('point_of_sale.ProductConfiguratorPopup', function(require) {
|
||||
const { useState, useSubEnv } = owl;
|
||||
|
||||
class ProductConfiguratorPopup extends AbstractAwaitablePopup {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useSubEnv({ attribute_components: [] });
|
||||
}
|
||||
|
||||
@@ -33,9 +33,8 @@ odoo.define('point_of_sale.ProductConfiguratorPopup', function(require) {
|
||||
Registries.Component.add(ProductConfiguratorPopup);
|
||||
|
||||
class BaseProductAttribute extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.env.attribute_components.push(this);
|
||||
|
||||
this.attribute = this.props.attribute;
|
||||
@@ -61,7 +60,11 @@ odoo.define('point_of_sale.ProductConfiguratorPopup', function(require) {
|
||||
}
|
||||
|
||||
class RadioProductAttribute extends BaseProductAttribute {
|
||||
mounted() {
|
||||
setup() {
|
||||
super.setup();
|
||||
owl.onMounted(this.onMounted);
|
||||
}
|
||||
onMounted() {
|
||||
// With radio buttons `t-model` selects the default input by searching for inputs with
|
||||
// a matching `value` attribute. In our case, we use `t-att-value` so `value` is
|
||||
// not found yet and no radio is selected by default.
|
||||
|
||||
@@ -14,10 +14,12 @@ odoo.define('point_of_sale.ProductInfoPopup', function(require) {
|
||||
* }
|
||||
*/
|
||||
class ProductInfoPopup extends AbstractAwaitablePopup {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
owl.onWillStart(this.onWillStart);
|
||||
owl.onMounted(this.onMounted);
|
||||
}
|
||||
async willStart() {
|
||||
async onWillStart() {
|
||||
const order = this.env.pos.get_order();
|
||||
try {
|
||||
// check back-end method `get_product_info_pos` to see what it returns
|
||||
@@ -52,7 +54,7 @@ odoo.define('point_of_sale.ProductInfoPopup', function(require) {
|
||||
/*
|
||||
* Since this popup need to be self dependent, in case of an error, the popup need to be closed on its own.
|
||||
*/
|
||||
mounted() {
|
||||
onMounted() {
|
||||
if (this.error) {
|
||||
this.cancel();
|
||||
if (this.error.message instanceof ConnectionLostError) {
|
||||
|
||||
@@ -26,8 +26,8 @@ odoo.define('point_of_sale.SelectionPopup', function (require) {
|
||||
* item: any,
|
||||
* }
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({ selectedId: this.props.list.find((item) => item.isSelected) });
|
||||
}
|
||||
selectItem(itemId) {
|
||||
|
||||
@@ -5,7 +5,7 @@ odoo.define('point_of_sale.TextAreaPopup', function(require) {
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { _lt } = require('@web/core/l10n/translation');
|
||||
|
||||
const { useRef, useState } = owl;
|
||||
const { onMounted, useRef, useState } = owl;
|
||||
|
||||
// formerly TextAreaPopupWidget
|
||||
// IMPROVEMENT: This code is very similar to TextInputPopup.
|
||||
@@ -15,12 +15,13 @@ odoo.define('point_of_sale.TextAreaPopup', function(require) {
|
||||
* @param {Object} props
|
||||
* @param {string} props.startingValue
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({ inputValue: this.props.startingValue });
|
||||
this.inputRef = useRef('input');
|
||||
onMounted(this.onMounted);
|
||||
}
|
||||
mounted() {
|
||||
onMounted() {
|
||||
this.inputRef.el.focus();
|
||||
}
|
||||
getPayload() {
|
||||
|
||||
@@ -5,16 +5,17 @@ odoo.define('point_of_sale.TextInputPopup', function(require) {
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { _lt } = require('@web/core/l10n/translation');
|
||||
|
||||
const { useRef, useState } = owl;
|
||||
const { onMounted, useRef, useState } = owl;
|
||||
|
||||
// formerly TextInputPopupWidget
|
||||
class TextInputPopup extends AbstractAwaitablePopup {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({ inputValue: this.props.startingValue });
|
||||
this.inputRef = useRef('input');
|
||||
onMounted(this.onMounted);
|
||||
}
|
||||
mounted() {
|
||||
onMounted() {
|
||||
this.inputRef.el.focus();
|
||||
}
|
||||
getPayload() {
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
odoo.define('point_of_sale.PosComponent', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { Component } = owl;
|
||||
const { Component, onRendered } = owl;
|
||||
|
||||
class PosComponent extends Component {
|
||||
setup() {
|
||||
onRendered(() => {
|
||||
if (this.env.isDebug()) {
|
||||
console.log('Rendered:', this.constructor.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* This function is available to all Components that inherit this class.
|
||||
* The goal of this function is to show an awaitable dialog (popup) that
|
||||
@@ -59,14 +66,6 @@ odoo.define('point_of_sale.PosComponent', function (require) {
|
||||
closeNotification() {
|
||||
this.trigger('close-notification');
|
||||
}
|
||||
render() {
|
||||
return super.render().then((res) => {
|
||||
if (this.env.isDebug()) {
|
||||
console.log('Rendered:', this.constructor.name);
|
||||
}
|
||||
return res;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return PosComponent;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
odoo.define('point_of_sale.PosContext', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { Context } = owl;
|
||||
const { reactive } = owl;
|
||||
|
||||
// Create global context objects
|
||||
// e.g. component.env.device = new Context({ isMobile: false });
|
||||
return {
|
||||
orderManagement: new Context({ searchString: '', selectedOrder: null }),
|
||||
orderManagement: reactive({ searchString: '', selectedOrder: null }),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,21 +6,25 @@ odoo.define('point_of_sale.ClientDetailsEdit', function(require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { onMounted, onWillUnmount } = owl;
|
||||
|
||||
class ClientDetailsEdit extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.intFields = ['country_id', 'state_id', 'property_product_pricelist'];
|
||||
const partner = this.props.partner;
|
||||
this.changes = {
|
||||
'country_id': partner.country_id && partner.country_id[0],
|
||||
'state_id': partner.state_id && partner.state_id[0],
|
||||
};
|
||||
}
|
||||
mounted() {
|
||||
this.env.bus.on('save-customer', this, this.saveChanges);
|
||||
}
|
||||
willUnmount() {
|
||||
this.env.bus.off('save-customer', this);
|
||||
|
||||
onMounted(() => {
|
||||
this.env.bus.on('save-customer', this, this.saveChanges);
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
this.env.bus.off('save-customer', this);
|
||||
});
|
||||
}
|
||||
get partnerImageUrl() {
|
||||
// We prioritize image_1920 in the `changes` field because we want
|
||||
|
||||
@@ -3,11 +3,13 @@ odoo.define('point_of_sale.ClientListScreen', function(require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
|
||||
const { Observer, debounce } = owl;
|
||||
const { isConnectionError } = require('point_of_sale.utils');
|
||||
|
||||
const { debounce } = require("@web/core/utils/timing");
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
|
||||
const { onWillUnmount } = owl;
|
||||
|
||||
/**
|
||||
* Render this screen using `showTempScreen` to select client.
|
||||
* When the shown screen is confirmed ('Set Customer' or 'Deselect Customer'
|
||||
@@ -24,8 +26,8 @@ odoo.define('point_of_sale.ClientListScreen', function(require) {
|
||||
* @props client - originally selected client
|
||||
*/
|
||||
class ClientListScreen extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click-save', () => this.env.bus.trigger('save-customer'));
|
||||
useListener('click-edit', () => this.editClient());
|
||||
useListener('save-changes', this.saveChanges);
|
||||
@@ -48,6 +50,7 @@ odoo.define('point_of_sale.ClientListScreen', function(require) {
|
||||
},
|
||||
};
|
||||
this.updateClientList = debounce(this.updateClientList, 70);
|
||||
onWillUnmount(this.updateClientList.cancel);
|
||||
}
|
||||
// Lifecycle hooks
|
||||
back() {
|
||||
@@ -110,8 +113,7 @@ odoo.define('point_of_sale.ClientListScreen', function(require) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
clickClient(event) {
|
||||
let partner = event.detail.client;
|
||||
clickClient(partner) {
|
||||
if (this.state.selectedClient === partner) {
|
||||
this.state.selectedClient = null;
|
||||
} else {
|
||||
@@ -142,16 +144,6 @@ odoo.define('point_of_sale.ClientListScreen', function(require) {
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
deactivateEditMode() {
|
||||
this.state.isEditMode = false;
|
||||
this.state.editModeProps = {
|
||||
partner: {
|
||||
country_id: this.env.pos.company.country_id,
|
||||
state_id: this.env.pos.company.state_id,
|
||||
},
|
||||
};
|
||||
this.render();
|
||||
}
|
||||
async saveChanges(event) {
|
||||
try {
|
||||
let partnerId = await this.rpc({
|
||||
@@ -174,9 +166,6 @@ odoo.define('point_of_sale.ClientListScreen', function(require) {
|
||||
}
|
||||
}
|
||||
}
|
||||
cancelEdit() {
|
||||
this.deactivateEditMode();
|
||||
}
|
||||
async searchClient() {
|
||||
let result = await this.getNewClient();
|
||||
this.env.pos.db.add_partners(result);
|
||||
|
||||
@@ -5,13 +5,13 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const { useErrorHandlers } = require('point_of_sale.custom_hooks');
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { isConnectionError } = require('point_of_sale.utils');
|
||||
|
||||
class PaymentScreen extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('delete-payment-line', this.deletePaymentLine);
|
||||
useListener('select-payment-line', this.selectPaymentLine);
|
||||
useListener('new-payment-line', this.addNewPaymentLine);
|
||||
|
||||
@@ -5,8 +5,8 @@ odoo.define('point_of_sale.PaymentScreenNumpad', function(require) {
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class PaymentScreenNumpad extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.decimalPoint = this.env._t.database.parameters.decimal_point;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -3,12 +3,12 @@ odoo.define('point_of_sale.OrderlineCustomerNoteButton', function(require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const ProductScreen = require('point_of_sale.ProductScreen');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class OrderlineCustomerNoteButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
async onClick() {
|
||||
|
||||
+3
-3
@@ -3,12 +3,12 @@ odoo.define('point_of_sale.ProductInfoButton', function(require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const ProductScreen = require('point_of_sale.ProductScreen');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class ProductInfoButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
onClick() {
|
||||
|
||||
+3
-3
@@ -4,11 +4,11 @@ odoo.define('point_of_sale.RefundButton', function (require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const ProductScreen = require('point_of_sale.ProductScreen');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
|
||||
class RefundButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this._onClick);
|
||||
}
|
||||
_onClick() {
|
||||
|
||||
+3
-3
@@ -3,12 +3,12 @@ odoo.define('point_of_sale.SetFiscalPositionButton', function(require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const ProductScreen = require('point_of_sale.ProductScreen');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class SetFiscalPositionButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
get currentOrder() {
|
||||
|
||||
+3
-3
@@ -3,12 +3,12 @@ odoo.define('point_of_sale.SetPricelistButton', function(require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const ProductScreen = require('point_of_sale.ProductScreen');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class SetPricelistButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
get currentOrder() {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
odoo.define('point_of_sale.OrderWidget', function(require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { onPatched, useRef } = owl;
|
||||
|
||||
class OrderWidget extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('select-line', this._selectLine);
|
||||
useListener('edit-pack-lot-lines', this._editPackLotLines);
|
||||
this.scrollableRef = useRef('scrollable');
|
||||
|
||||
@@ -4,7 +4,7 @@ odoo.define('point_of_sale.ProductScreen', function(require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const ControlButtonsMixin = require('point_of_sale.ControlButtonsMixin');
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { useBarcodeReader } = require('point_of_sale.custom_hooks');
|
||||
const { isConnectionError } = require('point_of_sale.utils');
|
||||
@@ -13,8 +13,8 @@ odoo.define('point_of_sale.ProductScreen', function(require) {
|
||||
const { onMounted, useState } = owl;
|
||||
|
||||
class ProductScreen extends ControlButtonsMixin(PosComponent) {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('update-selected-orderline', this._updateSelectedOrderline);
|
||||
useListener('set-numpad-mode', this._setNumpadMode);
|
||||
useListener('click-product', this._clickProduct);
|
||||
@@ -33,6 +33,7 @@ odoo.define('point_of_sale.ProductScreen', function(require) {
|
||||
triggerAtInput: 'update-selected-orderline',
|
||||
useWithBarcode: true,
|
||||
});
|
||||
onMounted(this.onMounted);
|
||||
// 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.
|
||||
@@ -41,7 +42,7 @@ odoo.define('point_of_sale.ProductScreen', function(require) {
|
||||
mobile_pane: this.props.mobile_pane || 'right',
|
||||
});
|
||||
}
|
||||
mounted() {
|
||||
onMounted() {
|
||||
this.env.posbus.trigger('start-cash-control');
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -2,26 +2,27 @@ odoo.define('point_of_sale.ProductsWidget', function(require) {
|
||||
'use strict';
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { useState } = owl;
|
||||
const { onWillUnmount, useState } = owl;
|
||||
|
||||
class ProductsWidget extends PosComponent {
|
||||
/**
|
||||
* @param {Object} props
|
||||
* @param {number?} props.startCategoryId
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('switch-category', this._switchCategory);
|
||||
useListener('update-search', this._updateSearch);
|
||||
useListener('try-add-product', this._tryAddProduct);
|
||||
useListener('clear-search', this._clearSearch);
|
||||
useListener('update-product-list', this._updateProductList);
|
||||
this.state = useState({ searchWord: '' });
|
||||
onWillUnmount(this.onWillUnmount);
|
||||
}
|
||||
willUnmount() {
|
||||
onWillUnmount() {
|
||||
this.trigger('toggle-mobile-searchbar', false);
|
||||
}
|
||||
get selectedCategoryId() {
|
||||
|
||||
+12
-10
@@ -5,22 +5,24 @@ odoo.define('point_of_sale.ProductsWidgetControlPanel', function(require) {
|
||||
const { ConnectionLostError, ConnectionAbortedError } = require('@web/core/network/rpc_service');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { debounce } = require("@web/core/utils/timing");
|
||||
|
||||
const { debounce, useRef } = owl;
|
||||
const { onMounted, onWillUnmount, useRef } = owl;
|
||||
|
||||
class ProductsWidgetControlPanel extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this.searchWordInput = useRef('search-word-input');
|
||||
this.updateSearch = debounce(this.updateSearch, 100);
|
||||
}
|
||||
mounted() {
|
||||
this.env.posbus.on('search-product-from-info-popup', this, this.searchProductFromInfo)
|
||||
}
|
||||
willUnmount() {
|
||||
this.env.posbus.off('search-product-from-info-popup', this);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
this.env.posbus.on('search-product-from-info-popup', this, this.searchProductFromInfo)
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
this.env.posbus.off('search-product-from-info-popup', this);
|
||||
});
|
||||
}
|
||||
clearSearch() {
|
||||
this.searchWordInput.el.value = '';
|
||||
this.trigger('clear-search');
|
||||
|
||||
@@ -4,13 +4,16 @@ odoo.define('point_of_sale.OrderReceipt', function(require) {
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { onWillUpdateProps } = owl;
|
||||
|
||||
class OrderReceipt extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
this._receiptEnv = this.props.order.getOrderReceiptEnv();
|
||||
}
|
||||
willUpdateProps(nextProps) {
|
||||
this._receiptEnv = nextProps.order.getOrderReceiptEnv();
|
||||
|
||||
onWillUpdateProps((nextProps) => {
|
||||
this._receiptEnv = nextProps.order.getOrderReceiptEnv();
|
||||
});
|
||||
}
|
||||
get receipt() {
|
||||
return this.receiptEnv.receipt;
|
||||
|
||||
@@ -7,11 +7,12 @@ odoo.define('point_of_sale.ReceiptScreen', function (require) {
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const AbstractReceiptScreen = require('point_of_sale.AbstractReceiptScreen');
|
||||
|
||||
const { useRef } = owl;
|
||||
const { onMounted, useRef, status } = owl;
|
||||
|
||||
const ReceiptScreen = (AbstractReceiptScreen) => {
|
||||
class ReceiptScreen extends AbstractReceiptScreen {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useErrorHandlers();
|
||||
this.orderReceipt = useRef('order-receipt');
|
||||
const order = this.currentOrder;
|
||||
@@ -19,20 +20,23 @@ odoo.define('point_of_sale.ReceiptScreen', function (require) {
|
||||
this.orderUiState = order.uiState.ReceiptScreen;
|
||||
this.orderUiState.inputEmail = this.orderUiState.inputEmail || (client && client.email) || '';
|
||||
this.is_email = is_email;
|
||||
}
|
||||
mounted() {
|
||||
// Here, we send a task to the event loop that handles
|
||||
// the printing of the receipt when the component is mounted.
|
||||
// We are doing this because we want the receipt screen to be
|
||||
// displayed regardless of what happen to the handleAutoPrint
|
||||
// call.
|
||||
setTimeout(async () => {
|
||||
let images = this.orderReceipt.el.getElementsByTagName('img');
|
||||
for(let image of images) {
|
||||
await image.decode();
|
||||
}
|
||||
await this.handleAutoPrint();
|
||||
}, 0);
|
||||
|
||||
onMounted(() => {
|
||||
// Here, we send a task to the event loop that handles
|
||||
// the printing of the receipt when the component is mounted.
|
||||
// We are doing this because we want the receipt screen to be
|
||||
// displayed regardless of what happen to the handleAutoPrint
|
||||
// call.
|
||||
setTimeout(async () => {
|
||||
if (status(this) === "mounted") {
|
||||
let images = this.orderReceipt.el.getElementsByTagName('img');
|
||||
for (let image of images) {
|
||||
await image.decode();
|
||||
}
|
||||
await this.handleAutoPrint();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
async onSendEmail() {
|
||||
if (!is_email(this.orderUiState.inputEmail)) {
|
||||
@@ -104,7 +108,7 @@ odoo.define('point_of_sale.ReceiptScreen', function (require) {
|
||||
}
|
||||
async _sendReceiptToCustomer() {
|
||||
const printer = new Printer(null, this.env.pos);
|
||||
const receiptString = this.orderReceipt.comp.el.outerHTML;
|
||||
const receiptString = this.orderReceipt.el.innerHTML;
|
||||
const ticketImage = await printer.htmlToImg(receiptString);
|
||||
const order = this.currentOrder;
|
||||
const client = order.get_client();
|
||||
|
||||
@@ -5,23 +5,25 @@ odoo.define('point_of_sale.ScaleScreen', function(require) {
|
||||
const { round_precision: round_pr } = require('web.utils');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const { useExternalListener, useState } = owl;
|
||||
const { onMounted, onWillUnmount, useExternalListener, useState } = owl;
|
||||
|
||||
class ScaleScreen extends PosComponent {
|
||||
/**
|
||||
* @param {Object} props
|
||||
* @param {Object} props.product The product to weight.
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useExternalListener(document, 'keyup', this._onHotkeys);
|
||||
this.state = useState({ weight: 0 });
|
||||
onMounted(this.onMounted);
|
||||
onWillUnmount(this.onWillUnmount);
|
||||
}
|
||||
mounted() {
|
||||
onMounted() {
|
||||
// start the scale reading
|
||||
this._readScale();
|
||||
}
|
||||
willUnmount() {
|
||||
onWillUnmount() {
|
||||
// stop the scale reading
|
||||
this.env.proxy_queue.clear();
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
odoo.define('point_of_sale.InvoiceButton', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const { isConnectionError } = require('point_of_sale.utils');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class InvoiceButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this._onClick);
|
||||
}
|
||||
get isAlreadyInvoiced() {
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
odoo.define('point_of_sale.ReprintReceiptButton', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class ReprintReceiptButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this._onClick);
|
||||
}
|
||||
async _onClick() {
|
||||
|
||||
@@ -6,7 +6,11 @@ odoo.define('point_of_sale.ReprintReceiptScreen', function (require) {
|
||||
|
||||
const ReprintReceiptScreen = (AbstractReceiptScreen) => {
|
||||
class ReprintReceiptScreen extends AbstractReceiptScreen {
|
||||
mounted() {
|
||||
setup() {
|
||||
super.setup();
|
||||
owl.onMounted(this.onMounted);
|
||||
}
|
||||
onMounted() {
|
||||
this.printReceipt();
|
||||
}
|
||||
confirm() {
|
||||
|
||||
@@ -5,14 +5,14 @@ odoo.define('point_of_sale.TicketScreen', function (require) {
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const IndependentToOrderScreen = require('point_of_sale.IndependentToOrderScreen');
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const { useListener, useAutofocus } = require('web.custom_hooks');
|
||||
const { useAutofocus, useListener } = require("@web/core/utils/hooks");
|
||||
const { parse } = require('web.field_utils');
|
||||
|
||||
const { useState } = owl;
|
||||
const { onMounted, onWillUnmount, useState } = owl;
|
||||
|
||||
class TicketScreen extends IndependentToOrderScreen {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('close-screen', this._onCloseScreen);
|
||||
useListener('filter-selected', this._onFilterSelected);
|
||||
useListener('search', this._onSearch);
|
||||
@@ -44,16 +44,19 @@ odoo.define('point_of_sale.TicketScreen', function (require) {
|
||||
selectedOrderlineIds: {},
|
||||
};
|
||||
Object.assign(this._state.ui, defaultUIState, this.props.ui || {});
|
||||
|
||||
onMounted(this.onMounted);
|
||||
onWillUnmount(this.onWillUnmount);
|
||||
}
|
||||
//#region LIFECYCLE METHODS
|
||||
mounted() {
|
||||
onMounted() {
|
||||
this.env.posbus.on('ticket-button-clicked', this, this.close);
|
||||
setTimeout(() => {
|
||||
// Show updated list of synced orders when going back to the screen.
|
||||
this._onFilterSelected({ detail: { filter: this._state.ui.filter } });
|
||||
});
|
||||
}
|
||||
willUnmount() {
|
||||
onWillUnmount() {
|
||||
this.env.posbus.off('ticket-button-clicked', this);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
odoo.define('point_of_sale.custom_hooks', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { Component, onMounted, onPatched, onWillUnmount } = owl;
|
||||
const { onMounted, onPatched, onWillUnmount, useComponent } = owl;
|
||||
|
||||
/**
|
||||
* Introduce error handlers in the component.
|
||||
@@ -10,7 +10,7 @@ odoo.define('point_of_sale.custom_hooks', function (require) {
|
||||
* the error when the order failed to sync.
|
||||
*/
|
||||
function useErrorHandlers() {
|
||||
const component = Component.current;
|
||||
const component = useComponent();
|
||||
|
||||
component._handlePushOrderError = async function (error) {
|
||||
// This error handler receives `error` equivalent to `error.message` of the rpc error.
|
||||
@@ -58,7 +58,7 @@ odoo.define('point_of_sale.custom_hooks', function (require) {
|
||||
}
|
||||
|
||||
function useAutoFocusToLast() {
|
||||
const current = Component.current;
|
||||
const current = useComponent();
|
||||
let target = null;
|
||||
function autofocus() {
|
||||
const prevTarget = target;
|
||||
@@ -74,7 +74,7 @@ odoo.define('point_of_sale.custom_hooks', function (require) {
|
||||
}
|
||||
|
||||
function useBarcodeReader(callbackMap, exclusive = false) {
|
||||
const current = Component.current;
|
||||
const current = useComponent();
|
||||
const barcodeReader = current.env.barcode_reader;
|
||||
for (let [key, callback] of Object.entries(callbackMap)) {
|
||||
callbackMap[key] = callback.bind(current);
|
||||
|
||||
@@ -9,6 +9,7 @@ var field_utils = require('web.field_utils');
|
||||
var time = require('web.time');
|
||||
var utils = require('web.utils');
|
||||
var { Gui } = require('point_of_sale.Gui');
|
||||
const { batched } = require("point_of_sale.utils");
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
@@ -16,7 +17,7 @@ var round_di = utils.round_decimals;
|
||||
var round_pr = utils.round_precision;
|
||||
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const reactivity = require('@point_of_sale/js/reactivity');
|
||||
const { markRaw, reactive } = owl;
|
||||
|
||||
// Container of the product images fetched during rendering
|
||||
// of customer display. There is no need to observe it, thus,
|
||||
@@ -87,7 +88,7 @@ class PosGlobalState extends PosModel {
|
||||
|
||||
this.db = new PosDB(); // a local database used to search trough products and categories & store pending orders
|
||||
this.debug = config.isDebug(); //debug mode
|
||||
this.unwatched = reactivity.markRaw({});
|
||||
this.unwatched = markRaw({});
|
||||
|
||||
// Business data; loaded from the server at launch
|
||||
this.company_logo = null;
|
||||
@@ -356,10 +357,10 @@ class PosGlobalState extends PosModel {
|
||||
options.json = json;
|
||||
}
|
||||
let order = Order.create({}, options);
|
||||
const batchedSaveToDb = reactivity.batched(() => {
|
||||
const batchedSaveToDb = batched(() => {
|
||||
order.save_to_db();
|
||||
});
|
||||
order = reactivity.reactive(order, batchedSaveToDb);
|
||||
order = reactive(order, batchedSaveToDb);
|
||||
order.save_to_db();
|
||||
return order;
|
||||
}
|
||||
@@ -521,7 +522,7 @@ class PosGlobalState extends PosModel {
|
||||
}
|
||||
|
||||
set customer_display(value) {
|
||||
this.unwatched.customer_display = reactivity.markRaw(value);
|
||||
this.unwatched.customer_display = markRaw(value);
|
||||
}
|
||||
|
||||
send_current_order_to_customer_facing_display() {
|
||||
@@ -1559,15 +1560,7 @@ class Orderline extends PosModel {
|
||||
}
|
||||
get_unit_display_price(){
|
||||
if (this.pos.config.iface_tax_included === 'total') {
|
||||
let price;
|
||||
// this is used to update the attribute object without triggering the rendering of the components
|
||||
reactivity.inSilentContext(() => {
|
||||
let quantity = this.quantity;
|
||||
this.quantity = 1.0;
|
||||
price = this.get_all_prices().priceWithTax;
|
||||
this.quantity = quantity;
|
||||
});
|
||||
return price;
|
||||
return this.get_all_prices(1).priceWithTax;
|
||||
} else {
|
||||
return this.get_unit_price();
|
||||
}
|
||||
@@ -1854,7 +1847,7 @@ class Orderline extends PosModel {
|
||||
'total_included': sign * round_pr(total_included, this.pos.currency.rounding),
|
||||
}
|
||||
}
|
||||
get_all_prices(){
|
||||
get_all_prices(qty = this.get_quantity()){
|
||||
var self = this;
|
||||
|
||||
var price_unit = this.get_unit_price() * (1.0 - (this.get_discount() / 100.0));
|
||||
@@ -1875,8 +1868,8 @@ class Orderline extends PosModel {
|
||||
});
|
||||
product_taxes = _.uniq(product_taxes, function(tax) { return tax.id; });
|
||||
|
||||
var all_taxes = this.compute_all(product_taxes, price_unit, this.get_quantity(), this.pos.currency.rounding);
|
||||
var all_taxes_before_discount = this.compute_all(product_taxes, this.get_unit_price(), this.get_quantity(), this.pos.currency.rounding);
|
||||
var all_taxes = this.compute_all(product_taxes, price_unit, qty, this.pos.currency.rounding);
|
||||
var all_taxes_before_discount = this.compute_all(product_taxes, this.get_unit_price(), qty, this.pos.currency.rounding);
|
||||
_(all_taxes.taxes).each(function(tax) {
|
||||
taxtotal += tax.amount;
|
||||
taxdetail[tax.id] = tax.amount;
|
||||
|
||||
@@ -15,7 +15,7 @@ let pos_env = Object.create(env);
|
||||
pos_env.proxy_queue = new devices.JobQueue(); // used to prevent parallels communications to the proxy
|
||||
pos_env.proxy = new devices.ProxyDevice({ env: pos_env }); // used to communicate to the hardware devices via a local proxy
|
||||
pos_env.barcode_reader = new BarcodeReader({ env: pos_env, proxy: pos_env.proxy });
|
||||
pos_env.posbus = new owl.core.EventBus();
|
||||
pos_env.posbus = new owl.EventBus();
|
||||
pos_env.posMutex = new concurrency.Mutex();
|
||||
|
||||
export default pos_env;
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
/* @odoo-module */
|
||||
/* global owl */
|
||||
|
||||
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
||||
const TARGET = Symbol('Target');
|
||||
// Escape hatch to prevent reactivity system to turn something into a reactive
|
||||
const SKIP = Symbol("Skip");
|
||||
// Special key to subscribe to, to be notified of key creation/deletion
|
||||
const KEYCHANGES = Symbol('Key changes');
|
||||
|
||||
let silent = false;
|
||||
|
||||
export function inSilentContext(cb) {
|
||||
silent = true;
|
||||
cb();
|
||||
silent = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given value can be made into a reactive object.
|
||||
*
|
||||
* @param value the value to check
|
||||
* @returns whether the value can be made reactive
|
||||
*/
|
||||
function canBeMadeReactive(value) {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
!(value instanceof Date) &&
|
||||
!(value instanceof Promise) &&
|
||||
!(value instanceof String) &&
|
||||
!(value instanceof Number) &&
|
||||
!(value instanceof Image) &&
|
||||
!(value instanceof Set)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an object or array so that it is ignored by the reactivity system
|
||||
*
|
||||
* @param value the value to mark
|
||||
* @returns the object itself
|
||||
*/
|
||||
export function markRaw(value) {
|
||||
value[SKIP] = true;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a reactive objet, return the raw (non reactive) underlying object
|
||||
*
|
||||
* @param value a reactive value
|
||||
* @returns the underlying value
|
||||
*/
|
||||
export function toRaw(value) {
|
||||
return value[TARGET];
|
||||
}
|
||||
|
||||
const targetToKeysToCallbacks = new WeakMap();
|
||||
/**
|
||||
* Observes a given key on a target with an callback. The callback will be
|
||||
* called when the given key changes on the target.
|
||||
*
|
||||
* @param target the target whose key should be observed
|
||||
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
||||
* or deletion)
|
||||
* @param callback the function to call when the key changes
|
||||
*/
|
||||
function observeTargetKey(target, key, callback) {
|
||||
if (!targetToKeysToCallbacks.get(target)) {
|
||||
targetToKeysToCallbacks.set(target, new Map());
|
||||
}
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||
if (!keyToCallbacks.get(key)) {
|
||||
keyToCallbacks.set(key, new Set());
|
||||
}
|
||||
keyToCallbacks.get(key).add(callback);
|
||||
if (!callbacksToTargets.has(callback)) {
|
||||
callbacksToTargets.set(callback, new Set());
|
||||
}
|
||||
callbacksToTargets.get(callback).add(target);
|
||||
}
|
||||
/**
|
||||
* Notify Reactives that are observing a given target that a key has changed on
|
||||
* the target.
|
||||
*
|
||||
* @param target target whose Reactives should be notified that the target was
|
||||
* changed.
|
||||
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
||||
* or deleted)
|
||||
*/
|
||||
function notifyReactives(target, key) {
|
||||
if (silent) return;
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||
if (!keyToCallbacks) {
|
||||
return;
|
||||
}
|
||||
const callbacks = keyToCallbacks.get(key);
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
// Loop on copy because clearReactivesForCallback will modify the set in place
|
||||
for (const callback of [...callbacks]) {
|
||||
clearReactivesForCallback(callback);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
const callbacksToTargets = new WeakMap();
|
||||
/**
|
||||
* Clears all subscriptions of the Reactives associated with a given callback.
|
||||
*
|
||||
* @param callback the callback for which the reactives need to be cleared
|
||||
*/
|
||||
export function clearReactivesForCallback(callback) {
|
||||
const targetsToClear = callbacksToTargets.get(callback);
|
||||
if (!targetsToClear) {
|
||||
return;
|
||||
}
|
||||
for (const target of targetsToClear) {
|
||||
const observedKeys = targetToKeysToCallbacks.get(target);
|
||||
if (!observedKeys) {
|
||||
continue;
|
||||
}
|
||||
for (const callbacks of observedKeys.values()) {
|
||||
callbacks.delete(callback);
|
||||
}
|
||||
}
|
||||
targetsToClear.clear();
|
||||
}
|
||||
|
||||
const reactiveCache = new WeakMap();
|
||||
/**
|
||||
* Creates a reactive proxy for an object. Reading data on the reactive object
|
||||
* subscribes to changes to the data. Writing data on the object will cause the
|
||||
* notify callback to be called if there are suscriptions to that data. Nested
|
||||
* objects and arrays are automatically made reactive as well.
|
||||
*
|
||||
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
||||
* you would like to be notified of any further changes, you should go read
|
||||
* the underlying data again. We assume that if you don't go read it again after
|
||||
* being notified, it means that you are no longer interested in that data.
|
||||
*
|
||||
* Subscriptions:
|
||||
* + Reading a property on an object will subscribe you to changes in the value
|
||||
* of that property.
|
||||
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
||||
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
||||
* key on the object with 'in' has the same effect.
|
||||
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
||||
* This is a choice that was made because changing a key's value will trigger
|
||||
* this trap and we do not want to subscribe by writes. This also means that
|
||||
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
||||
*
|
||||
* @param target the object for which to create a reactive proxy
|
||||
* @param callback the function to call when an observed property of the
|
||||
* reactive has changed
|
||||
* @returns a proxy that tracks changes to it
|
||||
*/
|
||||
export function reactive(target, callback) {
|
||||
if (!canBeMadeReactive(target)) {
|
||||
throw new Error(`Cannot make the given value reactive`);
|
||||
}
|
||||
if (SKIP in target) {
|
||||
return target;
|
||||
}
|
||||
const originalTarget = target[TARGET];
|
||||
if (originalTarget) {
|
||||
return reactive(originalTarget, callback);
|
||||
}
|
||||
if (!reactiveCache.has(target)) {
|
||||
reactiveCache.set(target, new Map());
|
||||
}
|
||||
const reactivesForTarget = reactiveCache.get(target);
|
||||
if (!reactivesForTarget.has(callback)) {
|
||||
const proxy = new Proxy(target, {
|
||||
get(target, key, proxy) {
|
||||
if (key === TARGET) {
|
||||
return target;
|
||||
}
|
||||
observeTargetKey(target, key, callback);
|
||||
const value = Reflect.get(target, key, proxy);
|
||||
if (!canBeMadeReactive(value)) {
|
||||
return value;
|
||||
}
|
||||
return reactive(value, callback);
|
||||
},
|
||||
set(target, key, value, proxy) {
|
||||
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
||||
const originalValue = Reflect.get(target, key, proxy);
|
||||
const ret = Reflect.set(target, key, value, proxy);
|
||||
if (isNewKey) {
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
}
|
||||
// While Array length may trigger the set trap, it's not actually set by this
|
||||
// method but is updated behind the scenes, and the trap is not called with the
|
||||
// new value. We disable the "same-value-optimization" for it because of that.
|
||||
if (originalValue !== value || (Array.isArray(target) && key === 'length')) {
|
||||
notifyReactives(target, key);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
deleteProperty(target, key) {
|
||||
const ret = Reflect.deleteProperty(target, key);
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
notifyReactives(target, key);
|
||||
return ret;
|
||||
},
|
||||
ownKeys(target) {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.ownKeys(target);
|
||||
},
|
||||
has(target, key) {
|
||||
// TODOhis observes all key changes instead of only the presence of the argument key
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.has(target, key);
|
||||
},
|
||||
});
|
||||
reactivesForTarget.set(callback, proxy);
|
||||
}
|
||||
return reactivesForTarget.get(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a batched version of a callback so that all calls to it in the same
|
||||
* microtick will only call the original callback once.
|
||||
*
|
||||
* @param callback the callback to batch
|
||||
* @returns a batched version of the original callback
|
||||
*/
|
||||
export function batched(callback) {
|
||||
let called = false;
|
||||
return async () => {
|
||||
// This await blocks all calls to the callback here, then releases them sequentially
|
||||
// in the next microtick. This line decides the granularity of the batch.
|
||||
await Promise.resolve();
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback();
|
||||
// wait for all calls in this microtick to fall through before resetting "called"
|
||||
// so that only the first call to the batched function calls the original callback
|
||||
await Promise.resolve();
|
||||
called = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const batchedRenderFunctions = new WeakMap();
|
||||
/**
|
||||
* Creates a reactive object that will be observed by the current component.
|
||||
* Reading data from the returned object (eg during rendering) will cause the
|
||||
* component to subscribe to that data and be rerendered when it changes.
|
||||
*
|
||||
* @param state the state to observe
|
||||
* @returns a reactive object that will cause the component to re-render on
|
||||
* relevant changes
|
||||
* @see reactive
|
||||
*/
|
||||
export function useState(state) {
|
||||
const node = owl.Component.current;
|
||||
if (!batchedRenderFunctions.has(node)) {
|
||||
batchedRenderFunctions.set(
|
||||
node,
|
||||
batched(() => node.render())
|
||||
);
|
||||
}
|
||||
const render = batchedRenderFunctions.get(node);
|
||||
const reactiveState = reactive(state, render);
|
||||
owl.hooks.onWillUnmount(() => clearReactivesForCallback(render));
|
||||
return reactiveState;
|
||||
}
|
||||
@@ -63,5 +63,29 @@ odoo.define('point_of_sale.utils', function (require) {
|
||||
return errorToHandle || error;
|
||||
}
|
||||
|
||||
return { getFileAsText, nextFrame, identifyError, isConnectionError };
|
||||
/**
|
||||
* Creates a batched version of a callback so that all calls to it in the same
|
||||
* microtick will only call the original callback once.
|
||||
*
|
||||
* @param callback the callback to batch
|
||||
* @returns a batched version of the original callback
|
||||
*/
|
||||
function batched(callback) {
|
||||
let called = false;
|
||||
return async () => {
|
||||
// This await blocks all calls to the callback here, then releases them sequentially
|
||||
// in the next microtick. This line decides the granularity of the batch.
|
||||
await Promise.resolve();
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback();
|
||||
// wait for all calls in this microtick to fall through before resetting "called"
|
||||
// so that only the first call to the batched function calls the original callback
|
||||
await Promise.resolve();
|
||||
called = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { getFileAsText, nextFrame, identifyError, isConnectionError, batched };
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="pos-topheader" t-att-class="{ oe_hidden: state.uiState !== 'READY' }">
|
||||
<div t-if="tempScreen.isShown" class="block-top-header" />
|
||||
<div class="pos-branding" t-if= "!env.isMobile">
|
||||
<img class="pos-logo" t-on-click="trigger('toggle-debug-widget')"
|
||||
<img class="pos-logo" t-on-click="() => this.trigger('toggle-debug-widget')"
|
||||
src="/point_of_sale/static/src/img/logo.png" alt="Logo" />
|
||||
<CashMoveButton t-if="showCashMoveButton()" />
|
||||
<TicketButton isTicketScreenShown="isTicketScreenShown" />
|
||||
@@ -40,22 +40,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DebugWidget t-if="env.isDebug() and state.debugWidgetIsShown" t-transition="fade" />
|
||||
<Transition t-if="env.isDebug()" visible="state.debugWidgetIsShown" name="'o-fade'" leaveDuration="200" t-slot-scope="transition">
|
||||
<DebugWidget className="transition.className"/>
|
||||
</Transition>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div t-if="['LOADING', 'CLOSING'].includes(state.uiState)" class="loader" t-transition="swing">
|
||||
<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">
|
||||
<h1 class="message">Connecting to devices</h1>
|
||||
<div class="button skip" t-on-click="trigger('loading-skip-callback')">
|
||||
Skip
|
||||
</div>
|
||||
</t>
|
||||
<Transition visible="['LOADING', 'CLOSING'].includes(state.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">
|
||||
<h1 class="message">Connecting to devices</h1>
|
||||
<div class="button skip" t-on-click="() => this.trigger('loading-skip-callback')">
|
||||
Skip
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Allow popups to be visible at any state of the ui. -->
|
||||
<div t-if="popup.isShown" class="popups">
|
||||
@@ -63,7 +67,9 @@
|
||||
t-key="popup.name" />
|
||||
</div>
|
||||
|
||||
<Notification t-if="state.notification.isShown" message="state.notification.message" duration="state.notification.duration" t-transition="fade"/>
|
||||
<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>
|
||||
|
||||
<NotificationSound t-if="state.sound.src" sound="state.sound" />
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
<t t-name="DebugWidget" owl="1">
|
||||
<Draggable limitArea="'.pos'">
|
||||
<div class="debug-widget">
|
||||
<div class="debug-widget" t-att-class="props.className">
|
||||
<header class="drag-handle">
|
||||
<h1>Debug Window</h1>
|
||||
</header>
|
||||
<div class="toggle" t-on-click="trigger('toggle-debug-widget')" title="Dismiss"
|
||||
<div class="toggle" t-on-click="() => this.trigger('toggle-debug-widget')" title="Dismiss"
|
||||
role="img" aria-label="Dismiss"><i class="fa fa-times" /></div>
|
||||
<div class="content">
|
||||
<p class="category">Electronic Scale</p>
|
||||
@@ -42,7 +42,7 @@
|
||||
Export Paid Orders
|
||||
</li>
|
||||
<a t-else="" t-att-download="paidOrdersFilename" t-att-href="paidOrdersURL"
|
||||
t-on-click="state.isPaidOrdersReady = !state.isPaidOrdersReady">
|
||||
t-on-click="() => { state.isPaidOrdersReady = !state.isPaidOrdersReady }">
|
||||
<li class="button">
|
||||
Download Paid Orders
|
||||
</li>
|
||||
@@ -53,7 +53,7 @@
|
||||
</li>
|
||||
<a t-else="" t-att-download="unpaidOrdersFilename"
|
||||
t-att-href="unpaidOrdersURL"
|
||||
t-on-click="state.isUnpaidOrdersReady = !state.isUnpaidOrdersReady">
|
||||
t-on-click="() => { state.isUnpaidOrdersReady = !state.isUnpaidOrdersReady }">
|
||||
<li class="button">
|
||||
Download Unpaid Orders
|
||||
</li>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ProxyStatus" owl="1">
|
||||
<div class="oe_status js_proxy" t-on-click="trigger('connect-to-proxy')">
|
||||
<div class="oe_status js_proxy" t-on-click="() => this.trigger('connect-to-proxy')">
|
||||
<span t-if="state.msg and !env.isMobile" class="js_msg">
|
||||
<t t-esc="state.msg" />
|
||||
</span>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
<t t-name="MobileOrderWidget" owl="1">
|
||||
<div class="switchpane">
|
||||
<button class="btn-switchpane" t-on-click="trigger('click-pay')">
|
||||
<button class="btn-switchpane" t-on-click="() => this.trigger('click-pay')">
|
||||
<h1>Pay</h1>
|
||||
<span><t t-esc="total" /></span>
|
||||
</button>
|
||||
<button class="btn-switchpane secondary" t-on-click="trigger('switchpane')">
|
||||
<button class="btn-switchpane secondary" t-on-click="props.onSwitchPane">
|
||||
<h1>Review</h1>
|
||||
<span><t t-esc="items_number"/> items</span>
|
||||
</button>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ul t-if="state.showSearchFields and state.searchInput" class="fields">
|
||||
<t t-foreach="searchFieldsList" t-as="fieldName" t-key="fieldName">
|
||||
<li t-att-class="{ highlight: fieldName == searchFieldsList[state.selectedSearchFieldId] }"
|
||||
t-on-click="trigger('click-search-field', fieldName)">
|
||||
t-on-click="() => this.trigger('click-search-field', fieldName)">
|
||||
<span class="field">
|
||||
<t t-esc="props.config.searchFields.get(fieldName)"></t>
|
||||
</span>
|
||||
@@ -23,7 +23,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div t-if="props.config.filter.show" class="filter radius-right"
|
||||
t-on-click.stop="state.showFilterOptions = !state.showFilterOptions">
|
||||
t-on-click.stop="() => { state.showFilterOptions = !state.showFilterOptions }">
|
||||
<span class="down-icon">
|
||||
<i class="fa fa-chevron-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
@@ -33,7 +33,7 @@
|
||||
<ul t-if="state.showFilterOptions" class="options">
|
||||
<t t-foreach="filterOptionsList" t-as="key" t-key="key">
|
||||
<t t-set="_option" t-value="props.config.filter.options.get(key)" />
|
||||
<li t-on-click="trigger('select-filter', key)" t-att-class="{ indented: _option.indented }">
|
||||
<li t-on-click="() => this.trigger('select-filter', key)" t-att-class="{ indented: _option.indented }">
|
||||
<t t-esc="_option.text"></t>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="Notification" owl="1">
|
||||
<div class="notification">
|
||||
<div class="notification" t-att-class="props.className">
|
||||
<span>
|
||||
<t t-esc="props.message" />
|
||||
</span>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<main class="body">
|
||||
<div class="cash-move">
|
||||
<div class="input-amount">
|
||||
<span t-on-click="onClickButton('in')" class="input-type" t-att-class="{ highlight: state.inputType == 'in' }">
|
||||
<span t-on-click="() => this.onClickButton('in')" class="input-type" t-att-class="{ highlight: state.inputType == 'in' }">
|
||||
Cash In
|
||||
</span>
|
||||
<span t-on-click="onClickButton('out')" class="input-type" t-att-class="{ highlight: state.inputType == 'out' }">
|
||||
<span t-on-click="() => this.onClickButton('out')" class="input-type" t-att-class="{ highlight: state.inputType == 'out' }">
|
||||
Cash Out
|
||||
</span>
|
||||
<div class="input-field">
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<main class="body">
|
||||
<div class="opening-cash-section">
|
||||
<span class="info-title">Opening cash</span>
|
||||
<div class="cash-input-sub-section" t-on-input="handleInputChange()">
|
||||
<div class="cash-input-sub-section" t-on-input="handleInputChange">
|
||||
<input class="pos-input" type="number" t-model.number="state.openingCash"/>
|
||||
<div class="button icon" t-on-click="openDetailsPopup()">
|
||||
<div class="button icon" t-on-click="openDetailsPopup">
|
||||
<i class="fa fa-calculator" role="img" title="Open the money details popup"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,10 +19,15 @@
|
||||
<textarea placeholder="Notes" class="opening-cash-notes" t-model="state.notes"/>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<div class="button" t-on-click="startSession()">Open session</div>
|
||||
<div class="button" t-on-click="startSession">Open session</div>
|
||||
</footer>
|
||||
</div>
|
||||
<MoneyDetailsPopup t-ref="moneyDetails" t-on-money-details-validated="updateCashOpening"/>
|
||||
<MoneyDetailsPopup
|
||||
t-if="state.displayMoneyDetailsPopup"
|
||||
manualInputCashCount="manualInputCashCount"
|
||||
onConfirm="(payload) => this.updateCashOpening(payload)"
|
||||
onDiscard.bind="closeDetailsPopup"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
<tr>
|
||||
<td t-esc="defaultCashDetails.name"/>
|
||||
<td t-esc="env.pos.format_currency(defaultCashDetails.amount)"/>
|
||||
<td class="flex" t-on-input="handleInputChange(defaultCashDetails.id)">
|
||||
<td class="flex" t-on-input="() => this.handleInputChange(defaultCashDetails.id)">
|
||||
<input class="pos-input" type="number" t-model.number="state.payments[defaultCashDetails.id].counted"/>
|
||||
<div class="button icon" t-on-click="openDetailsPopup()">
|
||||
<div class="button icon" t-on-click="openDetailsPopup">
|
||||
<i class="fa fa-calculator" role="img" title="Open the money details popup"/>
|
||||
</div>
|
||||
</td>
|
||||
@@ -79,7 +79,7 @@
|
||||
<td t-esc="pm.name"/>
|
||||
<td t-esc="env.pos.format_currency(pm.amount)"/>
|
||||
<t t-set="_showDiff" t-value="_getShowDiff(pm)" />
|
||||
<td t-if="_showDiff" t-on-input="handleInputChange(pm.id)"><input class="pos-input" type="number" t-model.number="state.payments[pm.id].counted"/></td>
|
||||
<td t-if="_showDiff" t-on-input="() => this.handleInputChange(pm.id)"><input class="pos-input" type="number" t-model.number="state.payments[pm.id].counted"/></td>
|
||||
<td t-if="_showDiff" t-esc="env.pos.format_currency(state.payments[pm.id].difference)" t-att-class="{'warning': state.payments[pm.id].difference}"/>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -94,12 +94,17 @@
|
||||
</div>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<div class="button" t-att-class="{'disabled': !canCancel()}" t-on-click="cancelPopup()">Continue Selling</div>
|
||||
<div class="button" t-on-click="closePos()">Keep Session Open</div>
|
||||
<div class="button" t-att-class="{'disabled': !canCloseSession()}" t-on-click="closeSession()">Close Session</div>
|
||||
<div class="button" t-att-class="{'disabled': !canCancel()}" t-on-click="cancelPopup">Continue Selling</div>
|
||||
<div class="button" t-on-click="closePos">Keep Session Open</div>
|
||||
<div class="button" t-att-class="{'disabled': !canCloseSession()}" t-on-click="closeSession">Close Session</div>
|
||||
</footer>
|
||||
</div>
|
||||
<MoneyDetailsPopup t-ref="moneyDetails" t-on-money-details-validated="updateCountedCash"/>
|
||||
<MoneyDetailsPopup
|
||||
t-if="state.displayMoneyDetailsPopup"
|
||||
manualInputCashCount="manualInputCashCount"
|
||||
onConfirm.bind="updateCountedCash"
|
||||
onDiscard.bind="closeDetailsPopup"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<input type="text" t-model="props.item.text" class="popup-input list-line-input"
|
||||
placeholder="Serial/Lot Number" t-on-keyup="onKeyup" />
|
||||
<i class="oe_link_icon fa fa-trash-o" role="img" aria-label="Remove" title="Remove"
|
||||
t-on-click="trigger('remove-item', props.item)"></i>
|
||||
t-on-click="() => this.trigger('remove-item', props.item)"></i>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div t-if="!props.exitButtonIsShown" class="button cancel" t-on-click="confirm">
|
||||
<t t-esc="props.confirmText" />
|
||||
</div>
|
||||
<div t-if="props.exitButtonIsShown" class="button cancel" t-on-click="trigger(props.exitButtonTrigger)">
|
||||
<div t-if="props.exitButtonIsShown" class="button cancel" t-on-click="() => this.trigger(props.exitButtonTrigger)">
|
||||
<t t-esc="props.exitButtonText" />
|
||||
</div>
|
||||
<a t-att-download="tracebackFilename" t-att-href="tracebackUrl">
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="MoneyDetailsPopup" owl="1">
|
||||
<div class="popup money-details invisible">
|
||||
<div class="popup money-details">
|
||||
<main class="body">
|
||||
<div class="money-details-title">
|
||||
Coins/Bills
|
||||
</div>
|
||||
<div class="money-details-info">
|
||||
<div t-foreach="[firstHalfMoneyDetails, lastHalfMoneyDetails]" t-as="moneyDetailsList">
|
||||
<div t-foreach="[firstHalfMoneyDetails, lastHalfMoneyDetails]" t-as="moneyDetailsList" t-key="moneyDetailsList_index">
|
||||
<t t-foreach="moneyDetailsList" t-as="moneyValue" t-key="moneyValue">
|
||||
<div class="money-details-value" t-on-input="updateMoneyDetailsAmount()">
|
||||
<div class="money-details-value" t-on-input="updateMoneyDetailsAmount">
|
||||
<input class="pos-input" t-att-id="moneyValue" type="number" t-model.number="state.moneyDetails[moneyValue]"/>
|
||||
<CurrencyAmount forTarget="moneyValue" currency="currency" amount="moneyValue"/>
|
||||
</div>
|
||||
@@ -22,8 +22,8 @@
|
||||
</div>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<div class="button" t-on-click="discard()">Discard</div>
|
||||
<div class="button" t-on-click="confirm()">Confirm</div>
|
||||
<div class="button" t-on-click="discard">Discard</div>
|
||||
<div class="button" t-on-click="confirm">Confirm</div>
|
||||
</footer>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -12,36 +12,36 @@
|
||||
<span t-att-class="{ 'highlight': state.toStartOver }"><t t-esc="inputBuffer"/></span>
|
||||
</div>
|
||||
<div class="popup-numpad">
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('1')">1</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('2')">2</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('3')">3</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('1')">1</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('2')">2</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('3')">3</button>
|
||||
<t t-if="props.cheap">
|
||||
<button class="mode-button add" t-on-mousedown.prevent="sendInput('+1')">+1</button>
|
||||
<button class="mode-button add" t-on-mousedown.prevent="() => this.sendInput('+1')">+1</button>
|
||||
</t>
|
||||
<t t-if="!props.cheap">
|
||||
<button class="mode-button add" t-on-mousedown.prevent="sendInput('+10')">+10</button>
|
||||
<button class="mode-button add" t-on-mousedown.prevent="() => this.sendInput('+10')">+10</button>
|
||||
</t>
|
||||
<br />
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('4')">4</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('5')">5</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('6')">6</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('4')">4</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('5')">5</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('6')">6</button>
|
||||
<t t-if="props.cheap">
|
||||
<button class="mode-button add" t-on-mousedown.prevent="sendInput('+2')">+2</button>
|
||||
<button class="mode-button add" t-on-mousedown.prevent="() => this.sendInput('+2')">+2</button>
|
||||
</t>
|
||||
<t t-if="!props.cheap">
|
||||
<button class="mode-button add" t-on-mousedown.prevent="sendInput('+20')">+20</button>
|
||||
<button class="mode-button add" t-on-mousedown.prevent="() => this.sendInput('+20')">+20</button>
|
||||
</t>
|
||||
<br />
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('7')">7</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('8')">8</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('9')">9</button>
|
||||
<button t-if="!props.isPassword" class="input-button number-char" t-on-mousedown.prevent="sendInput('-')">-</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('7')">7</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('8')">8</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('9')">9</button>
|
||||
<button t-if="!props.isPassword" class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('-')">-</button>
|
||||
<br />
|
||||
<button class="input-button numpad-char" t-on-mousedown.prevent="sendInput('Delete')">C</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="sendInput('0')">0</button>
|
||||
<button class="input-button number-char dot" t-on-mousedown.prevent="sendInput(decimalSeparator)">
|
||||
<button class="input-button numpad-char" t-on-mousedown.prevent="() => this.sendInput('Delete')">C</button>
|
||||
<button class="input-button number-char" t-on-mousedown.prevent="() => this.sendInput('0')">0</button>
|
||||
<button class="input-button number-char dot" t-on-mousedown.prevent="() => this.sendInput(decimalSeparator)">
|
||||
<t t-esc="decimalSeparator" /></button>
|
||||
<button class="input-button numpad-backspace" t-on-mousedown.prevent="sendInput('Backspace')">
|
||||
<button class="input-button numpad-backspace" t-on-mousedown.prevent="() => this.sendInput('Backspace')">
|
||||
<img style="pointer-events: none;"
|
||||
src="/point_of_sale/static/src/img/backspace.png" width="24"
|
||||
height="21" alt="Backspace" />
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</header>
|
||||
|
||||
<main class="body product_configurator_attributes col-lg-4 col-md-6 col-sm-12">
|
||||
<div t-foreach="props.attributes" t-as="attribute" class="attribute">
|
||||
<div t-foreach="props.attributes" t-as="attribute" t-key="attribute.id" class="attribute">
|
||||
<div class="attribute_name" t-esc="attribute.name"/>
|
||||
<RadioProductAttribute t-if="attribute.display_type === 'radio' or attribute.display_type === 'pills'" attribute="attribute"/>
|
||||
<SelectProductAttribute t-elif="attribute.display_type === 'select'" attribute="attribute"/>
|
||||
@@ -32,7 +32,7 @@
|
||||
<t t-name="RadioProductAttribute" owl="1">
|
||||
<div class="configurator_radio">
|
||||
<div class="table">
|
||||
<t t-foreach="values" t-as="value">
|
||||
<t t-foreach="values" t-as="value" t-key="value.id">
|
||||
<div class="attribute-name-cell">
|
||||
<input type="radio" t-model="state.selected_value" t-att-name="attribute.id"
|
||||
t-attf-id="{{ attribute.id }}_{{ value.id }}" t-att-value="value.id"/>
|
||||
@@ -62,7 +62,7 @@
|
||||
<t t-set="is_custom" t-value="false"/>
|
||||
|
||||
<select class="configurator_select" t-model="state.selected_value">
|
||||
<option t-foreach="values" t-as="value" t-att-value="value.id">
|
||||
<option t-foreach="values" t-as="value" t-key="value.id" t-att-value="value.id">
|
||||
<t t-set="is_custom" t-value="is_custom || (value.is_custom && value.id == state.selected_value)"/>
|
||||
<t t-esc="value.name"/>
|
||||
<t t-if="value.price_extra">
|
||||
@@ -80,7 +80,7 @@
|
||||
<t t-set="is_custom" t-value="false"/>
|
||||
|
||||
<ul class="color_attribute_list">
|
||||
<li t-foreach="values" t-as="value" class="color_attribute_list_item">
|
||||
<li t-foreach="values" t-as="value" t-key="value.id" class="color_attribute_list_item">
|
||||
<t t-set="is_custom" t-value="is_custom || (value.is_custom && value.id == state.selected_value)"/>
|
||||
<label t-attf-class="configurator_color {{ value.id == state.selected_value ? 'active' : '' }}"
|
||||
t-attf-style="background-color: {{ value.html_color }};" t-att-data-color="value.name">
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<td><span t-esc="variant.name" class="table-name"/>:</td>
|
||||
<td class="table-value">
|
||||
<t t-foreach="variant.values" t-as="attribute_value" t-key="attribute_value.name">
|
||||
<span class="searchable" t-on-click="searchProduct(attribute_value.search)"><t t-esc="attribute_value.name"/></span> <t t-if="attribute_value_index lt variant.values.length - 1">, </t>
|
||||
<span class="searchable" t-on-click="() => this.searchProduct(attribute_value.search)"><t t-esc="attribute_value.name"/></span> <t t-if="attribute_value_index lt variant.values.length - 1">, </t>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="selection scrollable-y">
|
||||
<t t-foreach="props.list" t-as="item" t-key="item.id">
|
||||
<div class="selection-item" t-att-class="{ selected: item.isSelected }"
|
||||
t-on-click="selectItem(item.id)">
|
||||
t-on-click="() => this.selectItem(item.id)">
|
||||
<t t-esc="item.label" />
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div>
|
||||
Payments:
|
||||
</div>
|
||||
<div t-foreach="payments" t-as="payment">
|
||||
<div t-foreach="payments" t-as="payment" t-key="payment_index">
|
||||
<t t-esc="payment.name" />
|
||||
<span t-esc="pos.format_currency_no_symbol(payment.total)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
<div>
|
||||
Taxes:
|
||||
</div>
|
||||
<div t-foreach="taxes" t-as="tax">
|
||||
<div t-foreach="taxes" t-as="tax" t-key="tax_index">
|
||||
<t t-esc="tax.name" />
|
||||
<span t-esc="pos.format_currency_no_symbol(tax.tax_amount)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
<t t-name="ClientLine" owl="1">
|
||||
<tr t-attf-class="client-line {{highlight}}" t-att-data-id="props.partner.id"
|
||||
t-on-click="trigger('click-client', {client: props.partner})">
|
||||
t-on-click="() => this.props.onClickClient(props.partner)">
|
||||
<td>
|
||||
<t t-esc="props.partner.name or ''" />
|
||||
<span t-if="highlight">
|
||||
<br/><button class="edit-client-button" t-on-click.stop="trigger('click-edit')">EDIT</button>
|
||||
<br/><button class="edit-client-button" t-on-click.stop="() => this.trigger('click-edit')">EDIT</button>
|
||||
</span>
|
||||
</td>
|
||||
<td t-if="!env.isMobile">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<t t-if="!env.isMobile"> Discard</t>
|
||||
</div>
|
||||
<div t-if="!state.detailIsShown && !state.selectedClient" class="button new-customer" role="img" aria-label="Add a customer"
|
||||
t-on-click="trigger('activate-edit-mode', { isNewClient: true })"
|
||||
t-on-click="() => this.trigger('activate-edit-mode', { isNewClient: true })"
|
||||
title="Add a customer">
|
||||
<i class="fa fa-plus"/>
|
||||
<t t-if="!env.isMobile"> Create</t>
|
||||
@@ -25,7 +25,7 @@
|
||||
<i t-elif="nextButton.command === 'set'" class="fa fa-check"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="button" t-if="state.detailIsShown" t-on-click="trigger('click-save')">
|
||||
<div class="button" t-if="state.detailIsShown" t-on-click="() => this.trigger('click-save')">
|
||||
<i class="fa fa-floppy-o"/>
|
||||
<t t-if="!env.isMobile">
|
||||
<span> Save</span>
|
||||
@@ -35,7 +35,7 @@
|
||||
<input placeholder="Search Customers" size="1" t-on-keyup="updateClientList" />
|
||||
<span class="search-clear-client"></span>
|
||||
</div>
|
||||
<div class="button back" t-on-click="searchClient()" t-if="!state.detailIsShown">
|
||||
<div class="button back" t-on-click="searchClient" t-if="!state.detailIsShown">
|
||||
<span class="database-icon">
|
||||
<i class="fa fa-database"/>
|
||||
</span>
|
||||
@@ -51,8 +51,7 @@
|
||||
<section class="subwindow collapsed">
|
||||
<div class="subwindow-container collapsed">
|
||||
<div t-if="state.detailIsShown" class="client-details-contents subwindow-container-fix">
|
||||
<ClientDetailsEdit t-props="state.editModeProps"
|
||||
t-on-cancel-edit="cancelEdit"/>
|
||||
<ClientDetailsEdit t-props="state.editModeProps" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -76,7 +75,7 @@
|
||||
<ClientLine partner="partner"
|
||||
selectedClient="state.selectedClient"
|
||||
detailIsShown="state.detailIsShown"
|
||||
t-on-click-client="clickClient" />
|
||||
onClickClient.bind="clickClient"/>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="PSNumpadInputButton" owl="1">
|
||||
<button t-attf-class="{{ _class }}"
|
||||
t-on-click="trigger('input-from-numpad', { key: props.value })">
|
||||
t-on-click="() => this.trigger('input-from-numpad', { key: props.value })">
|
||||
<t t-slot="default">
|
||||
<t t-if="props.text">
|
||||
<t t-esc="props.text" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="PaymentMethodButton" owl="1">
|
||||
<div class="button paymentmethod"
|
||||
t-on-click="trigger('new-payment-line', props.paymentMethod)">
|
||||
t-on-click="() => this.trigger('new-payment-line', props.paymentMethod)">
|
||||
<div class="payment-name">
|
||||
<t t-esc="props.paymentMethod.name" />
|
||||
</div>
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<t t-if="!env.isMobile">
|
||||
<div class="top-content">
|
||||
<div class="button back"
|
||||
t-on-click="showScreen('ProductScreen')">
|
||||
t-on-click="() => this.showScreen('ProductScreen')">
|
||||
<i class="fa fa-angle-double-left fa-fw"></i>
|
||||
<span class="back_text">Back</span>
|
||||
</div>
|
||||
<div class="top-content-center"><h1>Payment</h1></div>
|
||||
<div class="button next" t-att-class="{ highlight: currentOrder.is_paid() and currentOrder._isValidEmptyOrder() }"
|
||||
t-on-click="trigger('validate-order')">
|
||||
t-on-click="() => this.trigger('validate-order')">
|
||||
<span class="next_text">Validate</span>
|
||||
<i class="fa fa-angle-double-right fa-fw"></i>
|
||||
</div>
|
||||
@@ -81,7 +81,7 @@
|
||||
</div>
|
||||
<div t-if="env.pos.config.ship_later" class="button"
|
||||
t-att-class="{ highlight: currentOrder.is_to_ship() }"
|
||||
t-on-click="toggleIsToShip()">
|
||||
t-on-click="toggleIsToShip">
|
||||
<i class="fa fa-clock-o" /> Ship Later
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,10 +93,10 @@
|
||||
<div class="switchpane">
|
||||
<button class="btn-switchpane"
|
||||
t-att-class="{ secondary: !(currentOrder.is_paid() and currentOrder._isValidEmptyOrder()) }"
|
||||
t-on-click="trigger('validate-order')">
|
||||
t-on-click="() => this.trigger('validate-order')">
|
||||
<h1>Validate</h1>
|
||||
</button>
|
||||
<button class="btn-switchpane secondary" t-on-click="showScreen('ProductScreen', {mobile_pane: 'left'})">
|
||||
<button class="btn-switchpane secondary" t-on-click="() => this.showScreen('ProductScreen', {mobile_pane: 'left'})">
|
||||
<h1>Review</h1>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+5
-5
@@ -7,7 +7,7 @@
|
||||
<div>
|
||||
Payment request pending
|
||||
</div>
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="trigger('send-payment-request', props.line)">
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="() => this.trigger('send-payment-request', props.line)">
|
||||
Send
|
||||
</div>
|
||||
</t>
|
||||
@@ -15,7 +15,7 @@
|
||||
<div>
|
||||
Transaction cancelled
|
||||
</div>
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="trigger('send-payment-request', props.line)">
|
||||
<div class="button send_payment_request highlight" title="Send Payment Request" t-on-click="() => this.trigger('send-payment-request', props.line)">
|
||||
Retry
|
||||
</div>
|
||||
</t>
|
||||
@@ -23,7 +23,7 @@
|
||||
<div>
|
||||
Connection error
|
||||
</div>
|
||||
<div class="button send_force_done" title="Force Done" t-on-click="trigger('send-force-done', props.line)">
|
||||
<div class="button send_force_done" title="Force Done" t-on-click="() => this.trigger('send-force-done', props.line)">
|
||||
Force done
|
||||
</div>
|
||||
</t>
|
||||
@@ -31,7 +31,7 @@
|
||||
<div>
|
||||
Waiting for card
|
||||
</div>
|
||||
<div class="button send_payment_cancel" title="Cancel Payment Request" t-on-click="trigger('send-payment-cancel', props.line)">
|
||||
<div class="button send_payment_cancel" title="Cancel Payment Request" t-on-click="() => this.trigger('send-payment-cancel', props.line)">
|
||||
Cancel
|
||||
</div>
|
||||
</t>
|
||||
@@ -56,7 +56,7 @@
|
||||
Payment Successful
|
||||
</div>
|
||||
<t t-if="props.line.can_be_reversed">
|
||||
<div class="button send_payment_reversal" title="Reverse Payment" t-on-click="trigger('send-payment-reverse', props.line)">
|
||||
<div class="button send_payment_reversal" title="Reverse Payment" t-on-click="() => this.trigger('send-payment-reverse', props.line)">
|
||||
Reverse
|
||||
</div>
|
||||
</t>
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@
|
||||
<t t-if="line.selected">
|
||||
<div class="paymentline selected"
|
||||
t-att-class="selectedLineClass(line)"
|
||||
t-on-click="trigger('select-payment-line', { cid: line.cid })">
|
||||
t-on-click="() => this.trigger('select-payment-line', { cid: line.cid })">
|
||||
<div class="payment-name">
|
||||
<t t-esc="line.payment_method.name" />
|
||||
</div>
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
<t t-if="!line.payment_status or !['done', 'reversed'].includes(line.payment_status)">
|
||||
<div class="delete-button"
|
||||
t-on-click="trigger('delete-payment-line', { cid: line.cid })"
|
||||
t-on-click="() => this.trigger('delete-payment-line', { cid: line.cid })"
|
||||
aria-label="Delete" title="Delete">
|
||||
<i class="fa fa-times-circle" />
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@
|
||||
<t t-else="">
|
||||
<div class="paymentline"
|
||||
t-att-class="unselectedLineClass(line)"
|
||||
t-on-click="trigger('select-payment-line', { cid: line.cid })">
|
||||
t-on-click="() => this.trigger('select-payment-line', { cid: line.cid })">
|
||||
<div class="payment-name">
|
||||
<t t-esc="line.payment_method.name" />
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
<t t-if="!line.payment_status or !['done', 'reversed'].includes(line.payment_status)">
|
||||
<div class="delete-button"
|
||||
t-on-click="trigger('delete-payment-line', { cid: line.cid })"
|
||||
t-on-click="() => this.trigger('delete-payment-line', { cid: line.cid })"
|
||||
aria-label="Delete" title="Delete">
|
||||
<i class="fa fa-times-circle" />
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<t t-name="ActionpadWidget" owl="1">
|
||||
<div class="actionpad">
|
||||
<button class="button set-customer" t-att-class="{'decentered': isLongName}"
|
||||
t-on-click="trigger('click-customer')">
|
||||
t-on-click="() => this.trigger('click-customer')">
|
||||
<t t-if="!env.isMobile"><i class="fa fa-user" role="img" aria-label="Customer" title="Customer" /></t>
|
||||
<t t-if="props.client">
|
||||
<t t-esc="props.client.name" />
|
||||
@@ -13,13 +13,13 @@
|
||||
Customer
|
||||
</t>
|
||||
</button>
|
||||
<button class="button pay" t-on-click="trigger(props.actionToTrigger or 'click-pay')">
|
||||
<button class="button pay" t-on-click="() => this.trigger(props.actionToTrigger or 'click-pay')">
|
||||
<div class="pay-circle" t-att-class="{ 'highlight': props.isActionButtonHighlighted }">
|
||||
<i class="fa fa-chevron-right" role="img" aria-label="Pay" title="Pay" />
|
||||
</div>
|
||||
<t t-esc="props.actionName" />
|
||||
</button>
|
||||
<button t-if="env.isMobile" class="button back-button" t-on-click="trigger('switchpane')">
|
||||
<button t-if="env.isMobile" class="button back-button" t-on-click="props.onSwitchPane">
|
||||
<i class="fa fa-angle-double-left"/>
|
||||
Back
|
||||
</button>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<img src="/point_of_sale/static/src/img/bc-arrow-big.png" class="breadcrumb-arrow"
|
||||
alt="Slash" />
|
||||
<span class="breadcrumb-button"
|
||||
t-on-click="trigger('switch-category', props.category.id)">
|
||||
t-on-click="() => this.trigger('switch-category', props.category.id)">
|
||||
<t t-esc="props.category.name"></t>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="CategoryButton" owl="1">
|
||||
<span class="category-button" t-on-click="trigger('switch-category', props.category.id)">
|
||||
<span class="category-button" t-on-click="() => this.trigger('switch-category', props.category.id)">
|
||||
<div class="category-img">
|
||||
<img t-att-src="imageUrl" alt="Category" />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="CategorySimpleButton" owl="1">
|
||||
<span class="category-simple-button"
|
||||
t-on-click="trigger('switch-category', props.category.id)">
|
||||
t-on-click="() => this.trigger('switch-category', props.category.id)">
|
||||
<t t-esc="props.category.name" />
|
||||
</span>
|
||||
</t>
|
||||
|
||||
@@ -3,37 +3,37 @@
|
||||
|
||||
<t t-name="NumpadWidget" owl="1">
|
||||
<div class="numpad">
|
||||
<button class="input-button number-char" t-on-click="sendInput('1')">1</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput('2')">2</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput('3')">3</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('1')">1</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('2')">2</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('3')">3</button>
|
||||
<button class="mode-button" t-att-class="{'selected-mode': props.activeMode === 'quantity'}"
|
||||
t-on-click="changeMode('quantity')">Qty</button>
|
||||
t-on-click="() => this.changeMode('quantity')">Qty</button>
|
||||
<br />
|
||||
<button class="input-button number-char" t-on-click="sendInput('4')">4</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput('5')">5</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput('6')">6</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('4')">4</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('5')">5</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('6')">6</button>
|
||||
<button class="mode-button" t-att-class="{
|
||||
'selected-mode': props.activeMode === 'discount',
|
||||
'disabled-mode': !hasManualDiscount
|
||||
}"
|
||||
t-att-disabled="!hasManualDiscount"
|
||||
t-on-click="changeMode('discount')">Disc</button>
|
||||
t-on-click="() => this.changeMode('discount')">Disc</button>
|
||||
<br />
|
||||
<button class="input-button number-char" t-on-click="sendInput('7')">7</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput('8')">8</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput('9')">9</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('7')">7</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('8')">8</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('9')">9</button>
|
||||
<button class="mode-button" t-att-class="{
|
||||
'selected-mode': props.activeMode === 'price',
|
||||
'disabled-mode': !hasPriceControlRights
|
||||
}" t-att-disabled="!hasPriceControlRights"
|
||||
t-on-click="changeMode('price')">Price</button>
|
||||
t-on-click="() => this.changeMode('price')">Price</button>
|
||||
<br />
|
||||
<button class="input-button numpad-minus" t-att-class="{ 'disabled-mode': props.disableSign }" t-on-click="sendInput('-')">+/-</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput('0')">0</button>
|
||||
<button class="input-button number-char" t-on-click="sendInput(decimalSeparator)">
|
||||
<button class="input-button numpad-minus" t-att-class="{ 'disabled-mode': props.disableSign }" t-on-click="() => this.sendInput('-')">+/-</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput('0')">0</button>
|
||||
<button class="input-button number-char" t-on-click="() => this.sendInput(decimalSeparator)">
|
||||
<t t-esc="decimalSeparator" />
|
||||
</button>
|
||||
<button class="input-button numpad-backspace" t-on-click="sendInput('Backspace')">
|
||||
<button class="input-button numpad-backspace" t-on-click="() => this.sendInput('Backspace')">
|
||||
<img style="pointer-events: none;" src="/point_of_sale/static/src/img/backspace.png"
|
||||
width="24" height="21" alt="Backspace" />
|
||||
</button>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
<t t-name="ProductItem" owl="1">
|
||||
<article class="product" tabindex="0" t-on-keypress="spaceClickProduct"
|
||||
t-on-click="trigger('click-product', props.product)"
|
||||
t-on-click="() => this.trigger('click-product', props.product)"
|
||||
t-att-data-product-id="props.product.id"
|
||||
t-attf-aria-labelledby="article_product_{{props.product.id}}">
|
||||
<div class="product-img">
|
||||
<i role="img" aria-label="Info" title="Info" class="product-info-button fa fa-info-circle"
|
||||
t-on-click.stop="onProductInfoClick()"
|
||||
t-on-click.stop="onProductInfoClick"
|
||||
/>
|
||||
<img t-att-src="imageUrl" t-att-alt="props.product.display_name" />
|
||||
<span class="price-tag">
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
</div>
|
||||
<div class="subpads">
|
||||
<t t-set="_actionName">Payment</t>
|
||||
<ActionpadWidget client="client" actionName="_actionName" t-on-switchpane="switchPane" />
|
||||
<ActionpadWidget client="client" actionName="_actionName" onSwitchPane.bind="switchPane" />
|
||||
<NumpadWidget activeMode="env.pos.numpadMode" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightpane" t-if="!env.isMobile || state.mobile_pane === 'right'">
|
||||
<ProductsWidget mobileSearchBarIsShown="props.mobileSearchBarIsShown"/>
|
||||
<MobileOrderWidget t-if="env.isMobile" pane="state.mobile_pane" t-on-switchpane="switchPane"/>
|
||||
<MobileOrderWidget t-if="env.isMobile" pane="state.mobile_pane" onSwitchPane.bind="switchPane"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="breadcrumbs">
|
||||
<span class="breadcrumb">
|
||||
<span class="breadcrumb-button breadcrumb-home" t-on-click="trigger('switch-category', 0)">
|
||||
<span class="breadcrumb-button breadcrumb-home" t-on-click="() => this.trigger('switch-category', 0)">
|
||||
<i class="fa fa-home" role="img" aria-label="Home" title="Home"></i>
|
||||
</span>
|
||||
</span>
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<Portal target="'.pos .search-bar-portal'">
|
||||
<t t-portal="'.pos .search-bar-portal'">
|
||||
<div class="search-box">
|
||||
<span t-if="env.isMobile" class="icon" t-on-click="_toggleMobileSearchbar">
|
||||
<i t-attf-class="fa {{props.mobileSearchBarIsShown ? 'fa-arrow-left' : 'fa-search'}}"/>
|
||||
@@ -56,7 +56,7 @@
|
||||
<input t-ref="search-word-input" autofocus="autofocus" type="text" placeholder="Search Products..." t-on-keyup="updateSearch" />
|
||||
</t>
|
||||
</div>
|
||||
</Portal>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<div><t t-esc="receipt.company.website" /></div>
|
||||
</t>
|
||||
<t t-if="receipt.header_html">
|
||||
<t t-raw="receipt.header_html" />
|
||||
<t t-out="receipt.header_html" />
|
||||
</t>
|
||||
<t t-if="!receipt.header_html and receipt.header">
|
||||
<div style="white-space:pre-line"><t t-esc="receipt.header" /></div>
|
||||
@@ -124,7 +124,7 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<div t-if="receipt.footer_html" class="pos-receipt-center-align">
|
||||
<t t-raw="receipt.footer_html" />
|
||||
<t t-out="receipt.footer_html" />
|
||||
</div>
|
||||
|
||||
<div t-if="!receipt.footer_html and receipt.footer" class="pos-receipt-center-align" style="white-space:pre-line">
|
||||
@@ -135,11 +135,11 @@
|
||||
</div>
|
||||
|
||||
<div class="after-footer">
|
||||
<t t-foreach="receipt.paymentlines" t-as="line">
|
||||
<t t-foreach="receipt.paymentlines" t-as="line" t-key="line_index">
|
||||
<t t-if="line.ticket">
|
||||
<br />
|
||||
<div class="pos-payment-terminal-receipt">
|
||||
<t t-raw="line.ticket" />
|
||||
<t t-out="line.ticket" />
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pos-receipt-container">
|
||||
<OrderReceipt order="currentOrder" t-ref="order-receipt" />
|
||||
<div class="pos-receipt-container" t-ref="order-receipt">
|
||||
<OrderReceipt order="currentOrder"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-1
@@ -3,7 +3,9 @@
|
||||
|
||||
<t t-name="WrappedProductNameLines" owl="1">
|
||||
<span>
|
||||
<t t-foreach="props.line.product_name_wrapped.slice(1)" t-as="wrapped_line"><t t-esc="wrapped_line"/></t>
|
||||
<t t-foreach="props.line.product_name_wrapped.slice(1)" t-as="wrapped_line" t-key="wrapped_line_index">
|
||||
<t t-esc="wrapped_line"/>
|
||||
</t>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="OrderlineDetails" owl="1">
|
||||
<li class="orderline" t-on-click="trigger('click-order-line', props.line)" t-att-class="{ selected: props.isSelected }">
|
||||
<li class="orderline" t-on-click="() => this.trigger('click-order-line', props.line)" t-att-class="{ selected: props.isSelected }">
|
||||
<span class="product-name">
|
||||
<t t-esc="productName" />
|
||||
</span>
|
||||
@@ -35,7 +35,7 @@
|
||||
<t t-set="_destinationOrderUid" t-value="getToRefundDetail().destinationOrderUid"/>
|
||||
<t t-if="_destinationOrderUid">
|
||||
<t t-esc="getRefundingMessage()" />
|
||||
<span t-esc="_destinationOrderUid" class="order-link" t-on-click.stop="trigger('click-refund-order-uid', _destinationOrderUid)" />
|
||||
<span t-esc="_destinationOrderUid" class="order-link" t-on-click.stop="() => this.trigger('click-refund-order-uid', _destinationOrderUid)" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="getToRefundMessage()" />
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<div class="button print" t-on-click="tryReprint">
|
||||
<i class="fa fa-print"></i> Print Receipt
|
||||
</div>
|
||||
<div class="pos-receipt-container">
|
||||
<OrderReceipt order="props.order" t-ref="order-receipt" />
|
||||
<div class="pos-receipt-container" t-ref="order-receipt">
|
||||
<OrderReceipt order="props.order"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="controls">
|
||||
<t t-if="!env.isMobile || !state.showSearchBar">
|
||||
<div class="buttons">
|
||||
<button class="discard" t-on-click="trigger('close-screen')">
|
||||
<button class="discard" t-on-click="() => this.trigger('close-screen')">
|
||||
<span class="search-icon">
|
||||
<i class="fa fa-angle-double-left"/>
|
||||
</span>
|
||||
@@ -16,19 +16,19 @@
|
||||
Back
|
||||
</t>
|
||||
</button>
|
||||
<button t-if="shouldShowNewOrderButton()" class="highlight" t-on-click="trigger('create-new-order')">New Order</button>
|
||||
<button t-if="shouldShowNewOrderButton()" class="highlight" t-on-click="() => this.trigger('create-new-order')">New Order</button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="env.isMobile">
|
||||
<t t-if="state.showSearchBar">
|
||||
<button class="arrow-left" t-on-click="state.showSearchBar = !state.showSearchBar;">
|
||||
<button class="arrow-left" t-on-click="() => { state.showSearchBar = !state.showSearchBar }">
|
||||
<span class="search-icon">
|
||||
<i class="fa fa-angle-double-left"/>
|
||||
</span>
|
||||
</button>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<button class="search" t-on-click="state.showSearchBar = !state.showSearchBar;">
|
||||
<button class="search" t-on-click="() => { state.showSearchBar = !state.showSearchBar }">
|
||||
<span class="search-icon">
|
||||
<i class="fa fa-search"/>
|
||||
</span>
|
||||
@@ -39,10 +39,10 @@
|
||||
<SearchBar t-if="state.showSearchBar" config="getSearchBarConfig()" placeholder="_placeholder" />
|
||||
<div t-if="shouldShowPageControls()" class="item">
|
||||
<div class="page-controls">
|
||||
<div class="previous" t-on-click="trigger('prev-page')">
|
||||
<div class="previous" t-on-click="() => this.trigger('prev-page')">
|
||||
<i class="fa fa-fw fa-caret-left" role="img" aria-label="Previous Order List" title="Previous Order List"></i>
|
||||
</div>
|
||||
<div class="next" t-on-click="trigger('next-page')">
|
||||
<div class="next" t-on-click="() => this.trigger('next-page')">
|
||||
<i class="fa fa-fw fa-caret-right" role="img" aria-label="Next Order List" title="Next Order List"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,7 +65,7 @@
|
||||
<div class="col very-narrow" name="delete"></div>
|
||||
</div>
|
||||
<t t-foreach="_filteredOrderList" t-as="order" t-key="order.cid">
|
||||
<div class="order-row" t-att-class="{ highlight: isHighlighted(order) }" t-on-click="trigger('click-order', order)">
|
||||
<div class="order-row" t-att-class="{ highlight: isHighlighted(order) }" t-on-click="() => this.trigger('click-order', order)">
|
||||
<div class="col wide">
|
||||
<div t-if="env.isMobile">Date</div>
|
||||
<div><t t-esc="getDate(order)"></t></div>
|
||||
@@ -94,7 +94,7 @@
|
||||
<div t-if="env.isMobile">Status</div>
|
||||
<div><t t-esc="getStatus(order)"></t></div>
|
||||
</div>
|
||||
<div t-if="!shouldHideDeleteButton(order)" class="col very-narrow delete-button" name="delete" t-on-click.stop="trigger('delete-order', order)">
|
||||
<div t-if="!shouldHideDeleteButton(order)" class="col very-narrow delete-button" name="delete" t-on-click.stop="() => this.trigger('delete-order', order)">
|
||||
<i class="fa fa-trash" aria-hidden="true"/><t t-if="env.isMobile"> Delete</t>
|
||||
</div>
|
||||
<div t-else="" class="col very-narrow"></div>
|
||||
|
||||
@@ -38,7 +38,7 @@ odoo.define('point_of_sale.tour.pricelist', function (require) {
|
||||
var steps = [{ // Leave category displayed by default
|
||||
content: 'waiting for loading to finish',
|
||||
extra_trigger: 'body .pos:not(:has(.loader))', // Pos has finished loading
|
||||
trigger: '.o_loading_indicator:not(.o_loading)', // WebClient has finished Loading
|
||||
trigger: 'body:not(:has(.o_loading_indicator))', // WebClient has finished Loading
|
||||
run: function () {
|
||||
var product_wall_shelf = posmodel.db.search_product_in_category(0, 'Wall Shelf Unit')[0];
|
||||
var product_small_shelf = posmodel.db.search_product_in_category(0, 'Small Shelf')[0];
|
||||
|
||||
@@ -17,10 +17,6 @@ odoo.define('point_of_sale.test_env', async function (require) {
|
||||
// We override this method in the pos unit tests to prevent the unnecessary error in the web tests.
|
||||
cleanup.registerCleanup = () => {}
|
||||
|
||||
const { Portal } = owl;
|
||||
|
||||
Registries.Component.add(Portal);
|
||||
|
||||
await env.session.is_bound;
|
||||
const pos = PosGlobalState.create({ env });
|
||||
await pos.load_server_data();
|
||||
|
||||
@@ -4,6 +4,7 @@ odoo.define('point_of_sale.tests.NumberBuffer', function(require) {
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const makeTestEnvironment = require('web.test_env');
|
||||
const testUtils = require('web.test_utils');
|
||||
const { mount } = require('@web/../tests/helpers/utils');
|
||||
|
||||
const { Component, useState, xml } = owl;
|
||||
|
||||
@@ -13,10 +14,11 @@ odoo.define('point_of_sale.tests.NumberBuffer', function(require) {
|
||||
|
||||
QUnit.test('simple fast inputs with capture in between', async function(assert) {
|
||||
assert.expect(3);
|
||||
const target = testUtils.prepareTarget();
|
||||
const env = makeTestEnvironment();
|
||||
|
||||
class Root extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
setup() {
|
||||
this.state = useState({ buffer: '' });
|
||||
NumberBuffer.activate();
|
||||
NumberBuffer.use({
|
||||
@@ -28,24 +30,28 @@ odoo.define('point_of_sale.tests.NumberBuffer', function(require) {
|
||||
NumberBuffer.capture();
|
||||
NumberBuffer.reset();
|
||||
}
|
||||
onClickOne() {
|
||||
this.trigger('numpad-click-input', { key: '1' });
|
||||
}
|
||||
onClickTwo() {
|
||||
this.trigger('numpad-click-input', { key: '2' });
|
||||
}
|
||||
}
|
||||
Root.env = makeTestEnvironment();
|
||||
Root.template = xml/* html */ `
|
||||
<div>
|
||||
<p><t t-esc="state.buffer" /></p>
|
||||
<button class="one" t-on-click="trigger('numpad-click-input', { key: '1' })">1</button>
|
||||
<button class="two" t-on-click="trigger('numpad-click-input', { key: '2' })">2</button>
|
||||
<button class="one" t-on-click="onClickOne">1</button>
|
||||
<button class="two" t-on-click="onClickTwo">2</button>
|
||||
<button class="reset" t-on-click="resetBuffer">reset</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const root = new Root();
|
||||
await root.mount(testUtils.prepareTarget());
|
||||
await mount(Root, target, { env });
|
||||
|
||||
const oneButton = root.el.querySelector('button.one');
|
||||
const twoButton = root.el.querySelector('button.two');
|
||||
const resetButton = root.el.querySelector('button.reset');
|
||||
const bufferEl = root.el.querySelector('p');
|
||||
const oneButton = target.querySelector('button.one');
|
||||
const twoButton = target.querySelector('button.two');
|
||||
const resetButton = target.querySelector('button.reset');
|
||||
const bufferEl = target.querySelector('p');
|
||||
|
||||
testUtils.dom.click(oneButton);
|
||||
testUtils.dom.click(twoButton);
|
||||
@@ -58,8 +64,5 @@ odoo.define('point_of_sale.tests.NumberBuffer', function(require) {
|
||||
testUtils.dom.click(oneButton);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(bufferEl.textContent, '21');
|
||||
|
||||
root.unmount();
|
||||
root.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,12 +3,12 @@ odoo.define('pos_coupon.PromoCodeButton', function (require) {
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const ProductScreen = require('point_of_sale.ProductScreen');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { useListener } = require("@web/core/utils/hooks");
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
class PromoCodeButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
async onClick() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user