").html(renderedHtml);
+ $(this.env.pos.customer_display.document.body).html(
+ $renderedHtml.find(".pos-customer_facing_display")
+ );
+ $(this.env.pos.customer_display.document.head).html(
+ $renderedHtml.find(".resources").html()
+ );
+ }
+ async onClickProxy() {
+ try {
+ const renderedHtml = await this.env.pos.render_html_for_customer_facing_display();
+ let ownership = await this.env.proxy.take_ownership_over_customer_screen(renderedHtml);
+ if (typeof ownership === "string") {
+ ownership = JSON.parse(ownership);
+ }
+ if (ownership.status === "success") {
+ this.state.status = "success";
+ } else {
+ this.state.status = "warning";
+ }
+ if (!this.env.proxy.posbox_supports_display) {
+ this.env.proxy.posbox_supports_display = true;
+ this._start();
+ }
+ } catch (error) {
+ if (typeof error == "undefined") {
+ this.state.status = "failure";
+ } else {
+ this.state.status = "not_found";
+ }
+ }
+ }
+ _start() {
+ if (this.local) {
+ return;
+ }
- Registries.Component.add(CustomerFacingDisplayButton);
+ const self = this;
+ async function loop() {
+ if (self.env.proxy.posbox_supports_display) {
+ try {
+ let ownership = await self.env.proxy.test_ownership_of_customer_screen();
+ if (typeof ownership === "string") {
+ ownership = JSON.parse(ownership);
+ }
+ if (ownership.status === "OWNER") {
+ self.state.status = "success";
+ } else {
+ self.state.status = "warning";
+ }
+ setTimeout(loop, 3000);
+ } catch (error) {
+ if (error.abort) {
+ // Stop the loop
+ return;
+ }
+ if (typeof error == "undefined") {
+ self.state.status = "failure";
+ } else {
+ self.state.status = "not_found";
+ self.env.proxy.posbox_supports_display = false;
+ }
+ setTimeout(loop, 3000);
+ }
+ }
+ }
+ loop();
+ }
+}
+CustomerFacingDisplayButton.template = "CustomerFacingDisplayButton";
- return CustomerFacingDisplayButton;
-});
+Registries.Component.add(CustomerFacingDisplayButton);
+
+export default CustomerFacingDisplayButton;
diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js b/addons/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js
index de0c1a83b0b..79f874d6bac 100644
--- a/addons/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js
+++ b/addons/point_of_sale/static/src/js/ChromeWidgets/DebugWidget.js
@@ -1,163 +1,161 @@
-odoo.define('point_of_sale.DebugWidget', function (require) {
- 'use strict';
+/** @odoo-module */
- const { getFileAsText } = require('point_of_sale.utils');
- const { parse } = require('web.field_utils');
- const NumberBuffer = require('point_of_sale.NumberBuffer');
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { getFileAsText } from "@point_of_sale/js/utils";
+import { parse } from "web.field_utils";
+import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { onMounted, onWillUnmount, useRef, useState } = owl;
+const { onMounted, onWillUnmount, useRef, useState } = owl;
- class DebugWidget extends PosComponent {
- setup() {
- super.setup();
- this.state = useState({
- barcodeInput: '',
- weightInput: '',
- isPaidOrdersReady: false,
- isUnpaidOrdersReady: false,
- buffer: NumberBuffer.get(),
- });
+class DebugWidget extends PosComponent {
+ setup() {
+ super.setup();
+ this.state = useState({
+ barcodeInput: "",
+ weightInput: "",
+ isPaidOrdersReady: false,
+ isUnpaidOrdersReady: false,
+ buffer: NumberBuffer.get(),
+ });
- // NOTE: Perhaps this can still be improved.
- // What we do here is loop thru the `event` elements
- // then we assign animation that happens when the event is triggered
- // in the proxy. E.g. if open_cashbox is sent, the open_cashbox element
- // changes color from '#6CD11D' to '#1E1E1E' for a duration of 2sec.
- this.eventElementsRef = {};
- this.animations = {};
- for (let eventName of ['open_cashbox', 'print_receipt', 'scale_read']) {
- this.eventElementsRef[eventName] = useRef(eventName);
- this.env.proxy.add_notification(
- eventName,
- (() => {
- if (this.animations[eventName]) {
- this.animations[eventName].cancel();
- }
- const eventElement = this.eventElementsRef[eventName].el;
- eventElement.style.backgroundColor = '#6CD11D';
- this.animations[eventName] = eventElement.animate(
- { backgroundColor: ['#6CD11D', '#1E1E1E'] },
- 2000
- );
- }).bind(this)
- );
- }
-
- onMounted(() => {
- NumberBuffer.on('buffer-update', this, this._onBufferUpdate);
- });
-
- onWillUnmount(() => {
- NumberBuffer.off('buffer-update', this, this._onBufferUpdate);
- });
- }
- toggleWidget() {
- this.state.isShown = !this.state.isShown;
- }
- setWeight() {
- var weightInKg = parse.float(this.state.weightInput);
- if (!isNaN(weightInKg)) {
- this.env.proxy.debug_set_weight(weightInKg);
- }
- }
- resetWeight() {
- this.state.weightInput = '';
- this.env.proxy.debug_reset_weight();
- }
- async barcodeScan() {
- await this.env.barcode_reader.scan(this.state.barcodeInput);
- }
- async barcodeScanEAN() {
- const ean = this.env.barcode_reader.barcode_parser.sanitize_ean(
- this.state.barcodeInput || '0'
+ // NOTE: Perhaps this can still be improved.
+ // What we do here is loop thru the `event` elements
+ // then we assign animation that happens when the event is triggered
+ // in the proxy. E.g. if open_cashbox is sent, the open_cashbox element
+ // changes color from '#6CD11D' to '#1E1E1E' for a duration of 2sec.
+ this.eventElementsRef = {};
+ this.animations = {};
+ for (const eventName of ["open_cashbox", "print_receipt", "scale_read"]) {
+ this.eventElementsRef[eventName] = useRef(eventName);
+ this.env.proxy.add_notification(
+ eventName,
+ (() => {
+ if (this.animations[eventName]) {
+ this.animations[eventName].cancel();
+ }
+ const eventElement = this.eventElementsRef[eventName].el;
+ eventElement.style.backgroundColor = "#6CD11D";
+ this.animations[eventName] = eventElement.animate(
+ { backgroundColor: ["#6CD11D", "#1E1E1E"] },
+ 2000
+ );
+ }).bind(this)
);
- this.state.barcodeInput = ean;
- await this.env.barcode_reader.scan(ean);
}
- async deleteOrders() {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Delete Paid Orders ?'),
- body: this.env._t(
- 'This operation will permanently destroy all paid orders from the local storage. You will lose all the data. This operation cannot be undone.'
- ),
- });
- if (confirmed) {
- this.env.pos.db.remove_all_orders();
- this.env.pos.set_synch('connected', 0);
- }
- }
- async deleteUnpaidOrders() {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Delete Unpaid Orders ?'),
- body: this.env._t(
- 'This operation will destroy all unpaid orders in the browser. You will lose all the unsaved data and exit the point of sale. This operation cannot be undone.'
- ),
- });
- if (confirmed) {
- this.env.pos.db.remove_all_unpaid_orders();
- window.location = '/';
- }
- }
- _createBlob(contents) {
- if (typeof contents !== 'string') {
- contents = JSON.stringify(contents, null, 2);
- }
- return new Blob([contents]);
- }
- // IMPROVEMENT: Duplicated codes for downloading paid and unpaid orders.
- // The implementation can be better.
- preparePaidOrders() {
- try {
- this.paidOrdersBlob = this._createBlob(this.env.pos.export_paid_orders());
- this.state.isPaidOrdersReady = true;
- } catch (error) {
- console.warn(error);
- }
- }
- get paidOrdersFilename() {
- return `${this.env._t('paid orders')} ${moment().format('YYYY-MM-DD-HH-mm-ss')}.json`;
- }
- get paidOrdersURL() {
- var URL = window.URL || window.webkitURL;
- return URL.createObjectURL(this.paidOrdersBlob);
- }
- prepareUnpaidOrders() {
- try {
- this.unpaidOrdersBlob = this._createBlob(this.env.pos.export_unpaid_orders());
- this.state.isUnpaidOrdersReady = true;
- } catch (error) {
- console.warn(error);
- }
- }
- get unpaidOrdersFilename() {
- return `${this.env._t('unpaid orders')} ${moment().format('YYYY-MM-DD-HH-mm-ss')}.json`;
- }
- get unpaidOrdersURL() {
- var URL = window.URL || window.webkitURL;
- return URL.createObjectURL(this.unpaidOrdersBlob);
- }
- async importOrders(event) {
- const file = event.target.files[0];
- if (file) {
- const report = this.env.pos.import_orders(await getFileAsText(file));
- await this.showPopup('OrderImportPopup', { report });
- }
- }
- refreshDisplay() {
- this.env.proxy.message('display_refresh', {});
- }
- _onBufferUpdate(buffer) {
- this.state.buffer = buffer;
- }
- get bufferRepr() {
- return `"${this.state.buffer}"`;
+
+ onMounted(() => {
+ NumberBuffer.on("buffer-update", this, this._onBufferUpdate);
+ });
+
+ onWillUnmount(() => {
+ NumberBuffer.off("buffer-update", this, this._onBufferUpdate);
+ });
+ }
+ toggleWidget() {
+ this.state.isShown = !this.state.isShown;
+ }
+ setWeight() {
+ var weightInKg = parse.float(this.state.weightInput);
+ if (!isNaN(weightInKg)) {
+ this.env.proxy.debug_set_weight(weightInKg);
}
}
- DebugWidget.template = 'DebugWidget';
+ resetWeight() {
+ this.state.weightInput = "";
+ this.env.proxy.debug_reset_weight();
+ }
+ async barcodeScan() {
+ await this.env.barcode_reader.scan(this.state.barcodeInput);
+ }
+ async barcodeScanEAN() {
+ const ean = this.env.barcode_reader.barcode_parser.sanitize_ean(
+ this.state.barcodeInput || "0"
+ );
+ this.state.barcodeInput = ean;
+ await this.env.barcode_reader.scan(ean);
+ }
+ async deleteOrders() {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Delete Paid Orders ?"),
+ body: this.env._t(
+ "This operation will permanently destroy all paid orders from the local storage. You will lose all the data. This operation cannot be undone."
+ ),
+ });
+ if (confirmed) {
+ this.env.pos.db.remove_all_orders();
+ this.env.pos.set_synch("connected", 0);
+ }
+ }
+ async deleteUnpaidOrders() {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Delete Unpaid Orders ?"),
+ body: this.env._t(
+ "This operation will destroy all unpaid orders in the browser. You will lose all the unsaved data and exit the point of sale. This operation cannot be undone."
+ ),
+ });
+ if (confirmed) {
+ this.env.pos.db.remove_all_unpaid_orders();
+ window.location = "/";
+ }
+ }
+ _createBlob(contents) {
+ if (typeof contents !== "string") {
+ contents = JSON.stringify(contents, null, 2);
+ }
+ return new Blob([contents]);
+ }
+ // IMPROVEMENT: Duplicated codes for downloading paid and unpaid orders.
+ // The implementation can be better.
+ preparePaidOrders() {
+ try {
+ this.paidOrdersBlob = this._createBlob(this.env.pos.export_paid_orders());
+ this.state.isPaidOrdersReady = true;
+ } catch (error) {
+ console.warn(error);
+ }
+ }
+ get paidOrdersFilename() {
+ return `${this.env._t("paid orders")} ${moment().format("YYYY-MM-DD-HH-mm-ss")}.json`;
+ }
+ get paidOrdersURL() {
+ var URL = window.URL || window.webkitURL;
+ return URL.createObjectURL(this.paidOrdersBlob);
+ }
+ prepareUnpaidOrders() {
+ try {
+ this.unpaidOrdersBlob = this._createBlob(this.env.pos.export_unpaid_orders());
+ this.state.isUnpaidOrdersReady = true;
+ } catch (error) {
+ console.warn(error);
+ }
+ }
+ get unpaidOrdersFilename() {
+ return `${this.env._t("unpaid orders")} ${moment().format("YYYY-MM-DD-HH-mm-ss")}.json`;
+ }
+ get unpaidOrdersURL() {
+ var URL = window.URL || window.webkitURL;
+ return URL.createObjectURL(this.unpaidOrdersBlob);
+ }
+ async importOrders(event) {
+ const file = event.target.files[0];
+ if (file) {
+ const report = this.env.pos.import_orders(await getFileAsText(file));
+ await this.showPopup("OrderImportPopup", { report });
+ }
+ }
+ refreshDisplay() {
+ this.env.proxy.message("display_refresh", {});
+ }
+ _onBufferUpdate(buffer) {
+ this.state.buffer = buffer;
+ }
+ get bufferRepr() {
+ return `"${this.state.buffer}"`;
+ }
+}
+DebugWidget.template = "DebugWidget";
- Registries.Component.add(DebugWidget);
+Registries.Component.add(DebugWidget);
- return DebugWidget;
-});
+export default DebugWidget;
diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js
index 54ff960e695..b94b7589e19 100644
--- a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js
+++ b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js
@@ -1,36 +1,36 @@
-odoo.define('point_of_sale.HeaderButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
- const { ConnectionLostError, ConnectionAbortedError } = require('@web/core/network/rpc_service')
- const { identifyError } = require('point_of_sale.utils');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
+import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service";
+import { identifyError } from "@point_of_sale/js/utils";
- // Previously HeaderButtonWidget
- // This is the close session button
- class HeaderButton extends PosComponent {
- async onClick() {
- try {
- const info = await this.env.pos.getClosePosInfo();
- this.showPopup('ClosePosPopup', { info: info, keepBehind: true });
- } catch (e) {
- if (identifyError(e) instanceof ConnectionAbortedError||ConnectionLostError) {
- this.showPopup('OfflineErrorPopup', {
- title: this.env._t('Network Error'),
- body: this.env._t('Please check your internet connection and try again.'),
- });
- } else {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Unknown Error'),
- body: this.env._t('An unknown error prevents us from getting closing information.'),
- });
- }
+// Previously HeaderButtonWidget
+// This is the close session button
+class HeaderButton extends PosComponent {
+ async onClick() {
+ try {
+ const info = await this.env.pos.getClosePosInfo();
+ this.showPopup("ClosePosPopup", { info: info, keepBehind: true });
+ } catch (e) {
+ if (identifyError(e) instanceof ConnectionAbortedError || ConnectionLostError) {
+ this.showPopup("OfflineErrorPopup", {
+ title: this.env._t("Network Error"),
+ body: this.env._t("Please check your internet connection and try again."),
+ });
+ } else {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Unknown Error"),
+ body: this.env._t(
+ "An unknown error prevents us from getting closing information."
+ ),
+ });
}
}
}
- HeaderButton.template = 'HeaderButton';
+}
+HeaderButton.template = "HeaderButton";
- Registries.Component.add(HeaderButton);
+Registries.Component.add(HeaderButton);
- return HeaderButton;
-});
+export default HeaderButton;
diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js b/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js
index 2dd9955be11..618749a59a8 100644
--- a/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js
+++ b/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js
@@ -1,83 +1,76 @@
-odoo.define('point_of_sale.ProxyStatus', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { onMounted, onWillUnmount, useState } = owl;
+const { onMounted, onWillUnmount, useState } = owl;
- // Previously ProxyStatusWidget
- class ProxyStatus extends PosComponent {
- setup() {
- super.setup();
- const initialProxyStatus = this.env.proxy.get('status');
- this.state = useState({
- status: initialProxyStatus.status,
- msg: initialProxyStatus.msg,
- });
- this.statuses = ['connected', 'connecting', 'disconnected', 'warning'];
- this.index = 0;
+// Previously ProxyStatusWidget
+class ProxyStatus extends PosComponent {
+ setup() {
+ super.setup();
+ const initialProxyStatus = this.env.proxy.get("status");
+ this.state = useState({
+ status: initialProxyStatus.status,
+ msg: initialProxyStatus.msg,
+ });
+ this.statuses = ["connected", "connecting", "disconnected", "warning"];
+ this.index = 0;
- onMounted(() => {
- this.env.proxy.on('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);
- }
- _setStatus(newStatus) {
- if (newStatus.status === 'connected') {
- var warning = false;
- var msg = '';
- if (this.env.pos.config.iface_scan_via_proxy) {
- var scannerStatus = newStatus.drivers.scanner
- ? newStatus.drivers.scanner.status
- : false;
- if (scannerStatus != 'connected' && scannerStatus != 'connecting') {
- warning = true;
- msg += this.env._t('Scanner');
- }
+ onWillUnmount(() => {
+ this.env.proxy.off("change:status", this, this._onChangeStatus);
+ });
+ }
+ _onChangeStatus(posProxy, statusChange) {
+ this._setStatus(statusChange.newValue);
+ }
+ _setStatus(newStatus) {
+ if (newStatus.status === "connected") {
+ var warning = false;
+ var msg = "";
+ if (this.env.pos.config.iface_scan_via_proxy) {
+ var scannerStatus = newStatus.drivers.scanner
+ ? newStatus.drivers.scanner.status
+ : false;
+ if (scannerStatus != "connected" && scannerStatus != "connecting") {
+ warning = true;
+ msg += this.env._t("Scanner");
}
- if (
- this.env.pos.config.iface_print_via_proxy ||
- this.env.pos.config.iface_cashdrawer
- ) {
- var printerStatus = newStatus.drivers.printer
- ? newStatus.drivers.printer.status
- : false;
- if (printerStatus != 'connected' && printerStatus != 'connecting') {
- warning = true;
- msg = msg ? msg + ' & ' : msg;
- msg += this.env._t('Printer');
- }
- }
- if (this.env.pos.config.iface_electronic_scale) {
- var scaleStatus = newStatus.drivers.scale
- ? newStatus.drivers.scale.status
- : false;
- if (scaleStatus != 'connected' && scaleStatus != 'connecting') {
- warning = true;
- msg = msg ? msg + ' & ' : msg;
- msg += this.env._t('Scale');
- }
- }
- msg = msg ? msg + ' ' + this.env._t('Offline') : msg;
-
- this.state.status = warning ? 'warning' : 'connected';
- this.state.msg = msg;
- } else {
- this.state.status = newStatus.status;
- this.state.msg = newStatus.msg || '';
}
+ if (this.env.pos.config.iface_print_via_proxy || this.env.pos.config.iface_cashdrawer) {
+ var printerStatus = newStatus.drivers.printer
+ ? newStatus.drivers.printer.status
+ : false;
+ if (printerStatus != "connected" && printerStatus != "connecting") {
+ warning = true;
+ msg = msg ? msg + " & " : msg;
+ msg += this.env._t("Printer");
+ }
+ }
+ if (this.env.pos.config.iface_electronic_scale) {
+ var scaleStatus = newStatus.drivers.scale ? newStatus.drivers.scale.status : false;
+ if (scaleStatus != "connected" && scaleStatus != "connecting") {
+ warning = true;
+ msg = msg ? msg + " & " : msg;
+ msg += this.env._t("Scale");
+ }
+ }
+ msg = msg ? msg + " " + this.env._t("Offline") : msg;
+
+ this.state.status = warning ? "warning" : "connected";
+ this.state.msg = msg;
+ } else {
+ this.state.status = newStatus.status;
+ this.state.msg = newStatus.msg || "";
}
}
- ProxyStatus.template = 'ProxyStatus';
+}
+ProxyStatus.template = "ProxyStatus";
- Registries.Component.add(ProxyStatus);
+Registries.Component.add(ProxyStatus);
- return ProxyStatus;
-});
+export default ProxyStatus;
diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js
index 3e9690fab70..7f847461c28 100644
--- a/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js
+++ b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js
@@ -1,39 +1,37 @@
-odoo.define('point_of_sale.SaleDetailsButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
- const { renderToString } = require('@web/core/utils/render');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
+import { renderToString } from "@web/core/utils/render";
- class SaleDetailsButton extends PosComponent {
- async onClick() {
- // IMPROVEMENT: Perhaps put this logic in a parent component
- // so that for unit testing, we can check if this simple
- // component correctly triggers an event.
- const saleDetails = await this.rpc({
- model: 'report.point_of_sale.report_saledetails',
- method: 'get_sale_details',
- args: [false, false, false, [this.env.pos.pos_session.id]],
+class SaleDetailsButton extends PosComponent {
+ async onClick() {
+ // IMPROVEMENT: Perhaps put this logic in a parent component
+ // so that for unit testing, we can check if this simple
+ // component correctly triggers an event.
+ const saleDetails = await this.rpc({
+ model: "report.point_of_sale.report_saledetails",
+ method: "get_sale_details",
+ args: [false, false, false, [this.env.pos.pos_session.id]],
+ });
+ const report = renderToString(
+ "SaleDetailsReport",
+ Object.assign({}, saleDetails, {
+ date: new Date().toLocaleString(),
+ pos: this.env.pos,
+ })
+ );
+ const printResult = await this.env.proxy.printer.print_receipt(report);
+ if (!printResult.successful) {
+ await this.showPopup("ErrorPopup", {
+ title: printResult.message.title,
+ body: printResult.message.body,
});
- const report = renderToString(
- 'SaleDetailsReport',
- Object.assign({}, saleDetails, {
- date: new Date().toLocaleString(),
- pos: this.env.pos,
- })
- );
- const printResult = await this.env.proxy.printer.print_receipt(report);
- if (!printResult.successful) {
- await this.showPopup('ErrorPopup', {
- title: printResult.message.title,
- body: printResult.message.body,
- });
- }
}
}
- SaleDetailsButton.template = 'SaleDetailsButton';
+}
+SaleDetailsButton.template = "SaleDetailsButton";
- Registries.Component.add(SaleDetailsButton);
+Registries.Component.add(SaleDetailsButton);
- return SaleDetailsButton;
-});
+export default SaleDetailsButton;
diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js b/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js
index 1744b69230f..b22c1dc0557 100644
--- a/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js
+++ b/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js
@@ -1,17 +1,15 @@
-odoo.define('point_of_sale.SyncNotification', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class SyncNotification extends PosComponent {
- onClick() {
- this.env.pos.push_orders(null, { show_error: true });
- }
+class SyncNotification extends PosComponent {
+ onClick() {
+ this.env.pos.push_orders(null, { show_error: true });
}
- SyncNotification.template = 'SyncNotification';
+}
+SyncNotification.template = "SyncNotification";
- Registries.Component.add(SyncNotification);
+Registries.Component.add(SyncNotification);
- return SyncNotification;
-});
+export default SyncNotification;
diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js
index 3466536e020..de90148561c 100644
--- a/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js
+++ b/addons/point_of_sale/static/src/js/ChromeWidgets/TicketButton.js
@@ -1,28 +1,26 @@
-odoo.define('point_of_sale.TicketButton', function (require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class TicketButton extends PosComponent {
- onClick() {
- if (this.props.isTicketScreenShown) {
- this.env.posbus.trigger('ticket-button-clicked');
- } else {
- this.showScreen('TicketScreen');
- }
- }
- get count() {
- if (this.env.pos) {
- return this.env.pos.get_order_list().length;
- } else {
- return 0;
- }
+class TicketButton extends PosComponent {
+ onClick() {
+ if (this.props.isTicketScreenShown) {
+ this.env.posbus.trigger("ticket-button-clicked");
+ } else {
+ this.showScreen("TicketScreen");
}
}
- TicketButton.template = 'TicketButton';
+ get count() {
+ if (this.env.pos) {
+ return this.env.pos.get_order_list().length;
+ } else {
+ return 0;
+ }
+ }
+}
+TicketButton.template = "TicketButton";
- Registries.Component.add(TicketButton);
+Registries.Component.add(TicketButton);
- return TicketButton;
-});
+export default TicketButton;
diff --git a/addons/point_of_sale/static/src/js/ClassRegistry.js b/addons/point_of_sale/static/src/js/ClassRegistry.js
index eed07fe31b1..24c66e25f54 100644
--- a/addons/point_of_sale/static/src/js/ClassRegistry.js
+++ b/addons/point_of_sale/static/src/js/ClassRegistry.js
@@ -1,262 +1,260 @@
-odoo.define('point_of_sale.ClassRegistry', function (require) {
- 'use strict';
+/** @odoo-module */
- /**
- * **Usage:**
- * ```
- * const Registry = new ClassRegistry();
- *
- * class A {}
- * Registry.add(A);
- *
- * const AExt1 = A => class extends A {}
- * Registry.extend(A, AExt1)
- *
- * const B = A => class extends A {}
- * Registry.addByExtending(B, A)
- *
- * const AExt2 = A => class extends A {}
- * Registry.extend(A, AExt2)
- *
- * Registry.get(A)
- * // above returns: AExt2 -> AExt1 -> A
- * // Basically, 'A' in the registry points to
- * // the inheritance chain above.
- *
- * Registry.get(B)
- * // above returns: B -> AExt2 -> AExt1 -> A
- * // Even though B extends A before applying all
- * // the extensions of A, when getting it from the
- * // registry, the return points to a class with
- * // inheritance chain that includes all the extensions
- * // of 'A'.
- *
- * Registry.freeze()
- * // Example 'B' above is lazy. Basically, it is only
- * // computed when we call `get` from the registry.
- * // If we know that no more dynamic inheritances will happen,
- * // we can freeze the registry and cache the final form
- * // of each class in the registry.
- * ```
- *
- * IMPROVEMENT:
- * * So far, mixin can be accomplished by creating a method
- * the takes a class and returns a class expression. This is then
- * used before the extends keyword like so:
- *
- * ```js
- * class A {}
- * Registry.add(A)
- * const Mixin = x => class extends x {}
- * // apply mixin
- * // |
- * // v
- * const B = x => class extends Mixin(x) {}
- * Registry.addByExtending(B, A)
- * ```
- *
- * In the example, `|B| => B -> Mixin -> A`, and this is pretty convenient
- * already. However, this can still be improved since classes are only
- * compiled after `Registry.freeze()`. Perhaps, we can make the
- * Mixins extensible as well, such as so:
- *
- * ```
- * class A {}
- * Registry.add(A)
- * const Mixin = x => class extends x {}
- * Registry.add(Mixin)
- * const OtherMixin = x => class extends x {}
- * Registry.add(OtherMixin)
- * const B = x => class extends x {}
- * Registry.addByExtending(B, A, [Mixin, OtherMixin])
- * const ExtendMixin = x => class extends x {}
- * Registry.extend(Mixin, ExtendMixin)
- * ```
- *
- * In the above, after `Registry.freeze()`,
- * `|B| => B -> OtherMixin -> ExtendMixin -> Mixin -> A`
- */
- class ClassRegistry {
- constructor() {
- // base name map
- this.baseNameMap = {};
- // Object that maps `baseClass` to the class implementation extended in-place.
- this.includedMap = new Map();
- // Object that maps `baseClassCB` to the array of callbacks to generate the extended class.
- this.extendedCBMap = new Map();
- // Object that maps `baseClassCB` extended class to the `baseClass` of its super in the includedMap.
- this.extendedSuperMap = new Map();
- // For faster access, we can `freeze` the registry so that instead of dynamically generating
- // the extended classes, it is taken from the cache instead.
- this.cache = new Map();
- }
- /**
- * Add a new class in the Registry.
- * @param {Function} baseClass `class`
- */
- add(baseClass) {
- this.includedMap.set(baseClass, []);
- this.baseNameMap[baseClass.name] = baseClass;
- }
- /**
- * Add a new class in the Registry based on other class
- * in the registry.
- * @param {Function} baseClassCB `class -> class`
- * @param {Function} base `class | class -> class`
- */
- addByExtending(baseClassCB, base) {
- this.extendedCBMap.set(baseClassCB, [baseClassCB]);
- this.extendedSuperMap.set(baseClassCB, base);
- this.baseNameMap[baseClassCB.name] = baseClassCB;
- }
- /**
- * Extend in-place a class in the registry. E.g.
- * ```
- * // Using the following notation:
- * // * |A| - compiled class in the registry
- * // * A - class or an extension callback
- * // * |A| => A2 -> A1 -> A
- * // - the above means, compiled class A
- * // points to the class inheritance derived from
- * // A2(A1(A))
- *
- * class A {};
- * Registry.add(A);
- * // |A| => A
- *
- * let A1 = x => class extends x {};
- * Registry.extend(A, A1);
- * // |A| => A1 -> A
- *
- * let B = x => class extends x {};
- * Registry.addByExtending(B, A);
- * // |B| => B -> |A|
- * // |B| => B -> A1 -> A
- *
- * let B1 = x => class extends x {};
- * Registry.extend(B, B1);
- * // |B| => B1 -> B -> |A|
- *
- * let C = x => class extends x {};
- * Registry.addByExtending(C, B);
- * // |C| => C -> |B|
- *
- * let B2 = x => class extends x {};
- * Registry.extend(B, B2);
- * // |B| => B2 -> B1 -> B -> |A|
- *
- * // Overall:
- * // |A| => A1 -> A
- * // |B| => B2 -> B1 -> B -> A1 -> A
- * // |C| => C -> B2 -> B1 -> B -> A1 -> A
- * ```
- * @param {Function} base `class | class -> class`
- * @param {Function} extensionCB `class -> class`
- */
- extend(base, extensionCB) {
- if (typeof base === 'string') {
- base = this.baseNameMap[base];
- }
- let extensionArray;
- if (this.includedMap.get(base)) {
- extensionArray = this.includedMap.get(base);
- } else if (this.extendedCBMap.get(base)) {
- extensionArray = this.extendedCBMap.get(base);
- } else {
- throw new Error(
- `'${base.name}' is not in the Registry. Add it to Registry before extending.`
- );
- }
- extensionArray.push(extensionCB);
- const locOfNewExtension = extensionArray.length - 1;
- const self = this;
- const oldCompiled = this.isFrozen ? this.cache.get(base) : null;
- return {
- remove() {
- extensionArray.splice(locOfNewExtension, 1);
- self._recompute(base, oldCompiled);
- },
- compile() {
- self._recompute(base);
- }
- };
- }
- _compile(base) {
- let res;
- if (this.includedMap.has(base)) {
- res = this.includedMap.get(base).reduce((acc, ext) => ext(acc), base);
- } else {
- const superClass = this.extendedSuperMap.get(base);
- const extensionCBs = this.extendedCBMap.get(base);
- res = extensionCBs.reduce((acc, ext) => ext(acc), this._compile(superClass));
- }
- Object.defineProperty(res, 'name', { value: base.name });
- return res;
- }
- /**
- * Return the compiled class (containing all the extensions) of the base class.
- * @param {Function} base `class | class -> class` function used in adding the class
- */
- get(base) {
- if (typeof base === 'string') {
- base = this.baseNameMap[base];
- }
- if (this.isFrozen) {
- return this.cache.get(base);
- }
- return this._compile(base);
- }
- /**
- * Uses the callbacks registered in the registry to compile the classes.
- */
- freeze() {
- // Step: Compile the `included classes`.
- for (let [baseClass, extensionCBs] of this.includedMap.entries()) {
- const compiled = extensionCBs.reduce((acc, ext) => ext(acc), baseClass);
- this.cache.set(baseClass, compiled);
- }
-
- // Step: Compile the `extended classes` based on `included classes`.
- // Also gather those the are based on `extended classes`.
- const remaining = [];
- for (let [baseClassCB, extensionCBArray] of this.extendedCBMap.entries()) {
- const compiled = this.cache.get(this.extendedSuperMap.get(baseClassCB));
- if (!compiled) {
- remaining.push([baseClassCB, extensionCBArray]);
- continue;
- }
- const extendedClass = extensionCBArray.reduce(
- (acc, extensionCB) => extensionCB(acc),
- compiled
- );
- this.cache.set(baseClassCB, extendedClass);
- }
-
- // Step: Compile the `extended classes` based on `extended classes`.
- for (let [baseClassCB, extensionCBArray] of remaining) {
- const compiled = this.cache.get(this.extendedSuperMap.get(baseClassCB));
- const extendedClass = extensionCBArray.reduce(
- (acc, extensionCB) => extensionCB(acc),
- compiled
- );
- this.cache.set(baseClassCB, extendedClass);
- }
-
- // Step: Set the name of the compiled classess
- for (let [base, compiledClass] of this.cache.entries()) {
- Object.defineProperty(compiledClass, 'name', { value: base.name });
- }
-
- // Step: Set the flag to true;
- this.isFrozen = true;
- }
- _recompute(base, old) {
- if (typeof base === 'string') {
- base = this.baseNameMap[base];
- }
- return old ? old : this._compile(base);
- }
+/**
+ * **Usage:**
+ * ```
+ * const Registry = new ClassRegistry();
+ *
+ * class A {}
+ * Registry.add(A);
+ *
+ * const AExt1 = A => class extends A {}
+ * Registry.extend(A, AExt1)
+ *
+ * const B = A => class extends A {}
+ * Registry.addByExtending(B, A)
+ *
+ * const AExt2 = A => class extends A {}
+ * Registry.extend(A, AExt2)
+ *
+ * Registry.get(A)
+ * // above returns: AExt2 -> AExt1 -> A
+ * // Basically, 'A' in the registry points to
+ * // the inheritance chain above.
+ *
+ * Registry.get(B)
+ * // above returns: B -> AExt2 -> AExt1 -> A
+ * // Even though B extends A before applying all
+ * // the extensions of A, when getting it from the
+ * // registry, the return points to a class with
+ * // inheritance chain that includes all the extensions
+ * // of 'A'.
+ *
+ * Registry.freeze()
+ * // Example 'B' above is lazy. Basically, it is only
+ * // computed when we call `get` from the registry.
+ * // If we know that no more dynamic inheritances will happen,
+ * // we can freeze the registry and cache the final form
+ * // of each class in the registry.
+ * ```
+ *
+ * IMPROVEMENT:
+ * * So far, mixin can be accomplished by creating a method
+ * the takes a class and returns a class expression. This is then
+ * used before the extends keyword like so:
+ *
+ * ```js
+ * class A {}
+ * Registry.add(A)
+ * const Mixin = x => class extends x {}
+ * // apply mixin
+ * // |
+ * // v
+ * const B = x => class extends Mixin(x) {}
+ * Registry.addByExtending(B, A)
+ * ```
+ *
+ * In the example, `|B| => B -> Mixin -> A`, and this is pretty convenient
+ * already. However, this can still be improved since classes are only
+ * compiled after `Registry.freeze()`. Perhaps, we can make the
+ * Mixins extensible as well, such as so:
+ *
+ * ```
+ * class A {}
+ * Registry.add(A)
+ * const Mixin = x => class extends x {}
+ * Registry.add(Mixin)
+ * const OtherMixin = x => class extends x {}
+ * Registry.add(OtherMixin)
+ * const B = x => class extends x {}
+ * Registry.addByExtending(B, A, [Mixin, OtherMixin])
+ * const ExtendMixin = x => class extends x {}
+ * Registry.extend(Mixin, ExtendMixin)
+ * ```
+ *
+ * In the above, after `Registry.freeze()`,
+ * `|B| => B -> OtherMixin -> ExtendMixin -> Mixin -> A`
+ */
+class ClassRegistry {
+ constructor() {
+ // base name map
+ this.baseNameMap = {};
+ // Object that maps `baseClass` to the class implementation extended in-place.
+ this.includedMap = new Map();
+ // Object that maps `baseClassCB` to the array of callbacks to generate the extended class.
+ this.extendedCBMap = new Map();
+ // Object that maps `baseClassCB` extended class to the `baseClass` of its super in the includedMap.
+ this.extendedSuperMap = new Map();
+ // For faster access, we can `freeze` the registry so that instead of dynamically generating
+ // the extended classes, it is taken from the cache instead.
+ this.cache = new Map();
}
+ /**
+ * Add a new class in the Registry.
+ * @param {Function} baseClass `class`
+ */
+ add(baseClass) {
+ this.includedMap.set(baseClass, []);
+ this.baseNameMap[baseClass.name] = baseClass;
+ }
+ /**
+ * Add a new class in the Registry based on other class
+ * in the registry.
+ * @param {Function} baseClassCB `class -> class`
+ * @param {Function} base `class | class -> class`
+ */
+ addByExtending(baseClassCB, base) {
+ this.extendedCBMap.set(baseClassCB, [baseClassCB]);
+ this.extendedSuperMap.set(baseClassCB, base);
+ this.baseNameMap[baseClassCB.name] = baseClassCB;
+ }
+ /**
+ * Extend in-place a class in the registry. E.g.
+ * ```
+ * // Using the following notation:
+ * // * |A| - compiled class in the registry
+ * // * A - class or an extension callback
+ * // * |A| => A2 -> A1 -> A
+ * // - the above means, compiled class A
+ * // points to the class inheritance derived from
+ * // A2(A1(A))
+ *
+ * class A {};
+ * Registry.add(A);
+ * // |A| => A
+ *
+ * let A1 = x => class extends x {};
+ * Registry.extend(A, A1);
+ * // |A| => A1 -> A
+ *
+ * let B = x => class extends x {};
+ * Registry.addByExtending(B, A);
+ * // |B| => B -> |A|
+ * // |B| => B -> A1 -> A
+ *
+ * let B1 = x => class extends x {};
+ * Registry.extend(B, B1);
+ * // |B| => B1 -> B -> |A|
+ *
+ * let C = x => class extends x {};
+ * Registry.addByExtending(C, B);
+ * // |C| => C -> |B|
+ *
+ * let B2 = x => class extends x {};
+ * Registry.extend(B, B2);
+ * // |B| => B2 -> B1 -> B -> |A|
+ *
+ * // Overall:
+ * // |A| => A1 -> A
+ * // |B| => B2 -> B1 -> B -> A1 -> A
+ * // |C| => C -> B2 -> B1 -> B -> A1 -> A
+ * ```
+ * @param {Function} base `class | class -> class`
+ * @param {Function} extensionCB `class -> class`
+ */
+ extend(base, extensionCB) {
+ if (typeof base === "string") {
+ base = this.baseNameMap[base];
+ }
+ let extensionArray;
+ if (this.includedMap.get(base)) {
+ extensionArray = this.includedMap.get(base);
+ } else if (this.extendedCBMap.get(base)) {
+ extensionArray = this.extendedCBMap.get(base);
+ } else {
+ throw new Error(
+ `'${base.name}' is not in the Registry. Add it to Registry before extending.`
+ );
+ }
+ extensionArray.push(extensionCB);
+ const locOfNewExtension = extensionArray.length - 1;
+ const self = this;
+ const oldCompiled = this.isFrozen ? this.cache.get(base) : null;
+ return {
+ remove() {
+ extensionArray.splice(locOfNewExtension, 1);
+ self._recompute(base, oldCompiled);
+ },
+ compile() {
+ self._recompute(base);
+ },
+ };
+ }
+ _compile(base) {
+ let res;
+ if (this.includedMap.has(base)) {
+ res = this.includedMap.get(base).reduce((acc, ext) => ext(acc), base);
+ } else {
+ const superClass = this.extendedSuperMap.get(base);
+ const extensionCBs = this.extendedCBMap.get(base);
+ res = extensionCBs.reduce((acc, ext) => ext(acc), this._compile(superClass));
+ }
+ Object.defineProperty(res, "name", { value: base.name });
+ return res;
+ }
+ /**
+ * Return the compiled class (containing all the extensions) of the base class.
+ * @param {Function} base `class | class -> class` function used in adding the class
+ */
+ get(base) {
+ if (typeof base === "string") {
+ base = this.baseNameMap[base];
+ }
+ if (this.isFrozen) {
+ return this.cache.get(base);
+ }
+ return this._compile(base);
+ }
+ /**
+ * Uses the callbacks registered in the registry to compile the classes.
+ */
+ freeze() {
+ // Step: Compile the `included classes`.
+ for (const [baseClass, extensionCBs] of this.includedMap.entries()) {
+ const compiled = extensionCBs.reduce((acc, ext) => ext(acc), baseClass);
+ this.cache.set(baseClass, compiled);
+ }
- return ClassRegistry;
-});
+ // Step: Compile the `extended classes` based on `included classes`.
+ // Also gather those the are based on `extended classes`.
+ const remaining = [];
+ for (const [baseClassCB, extensionCBArray] of this.extendedCBMap.entries()) {
+ const compiled = this.cache.get(this.extendedSuperMap.get(baseClassCB));
+ if (!compiled) {
+ remaining.push([baseClassCB, extensionCBArray]);
+ continue;
+ }
+ const extendedClass = extensionCBArray.reduce(
+ (acc, extensionCB) => extensionCB(acc),
+ compiled
+ );
+ this.cache.set(baseClassCB, extendedClass);
+ }
+
+ // Step: Compile the `extended classes` based on `extended classes`.
+ for (const [baseClassCB, extensionCBArray] of remaining) {
+ const compiled = this.cache.get(this.extendedSuperMap.get(baseClassCB));
+ const extendedClass = extensionCBArray.reduce(
+ (acc, extensionCB) => extensionCB(acc),
+ compiled
+ );
+ this.cache.set(baseClassCB, extendedClass);
+ }
+
+ // Step: Set the name of the compiled classess
+ for (const [base, compiledClass] of this.cache.entries()) {
+ Object.defineProperty(compiledClass, "name", { value: base.name });
+ }
+
+ // Step: Set the flag to true;
+ this.isFrozen = true;
+ }
+ _recompute(base, old) {
+ if (typeof base === "string") {
+ base = this.baseNameMap[base];
+ }
+ return old ? old : this._compile(base);
+ }
+}
+
+export default ClassRegistry;
diff --git a/addons/point_of_sale/static/src/js/ComponentRegistry.js b/addons/point_of_sale/static/src/js/ComponentRegistry.js
index 1e820782e15..1547aa930e0 100644
--- a/addons/point_of_sale/static/src/js/ComponentRegistry.js
+++ b/addons/point_of_sale/static/src/js/ComponentRegistry.js
@@ -1,29 +1,27 @@
-odoo.define('point_of_sale.ComponentRegistry', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const ClassRegistry = require('point_of_sale.ClassRegistry');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import ClassRegistry from "@point_of_sale/js/ClassRegistry";
- class ComponentRegistry extends ClassRegistry {
- freeze() {
- super.freeze();
- // Make sure PosComponent has the compiled classes.
- // This way, we don't need to explicitly declare that
- // a set of components is children of another.
- PosComponent.components = {};
- for (let [base, compiledClass] of this.cache.entries()) {
- PosComponent.components[base.name] = compiledClass;
- }
- }
- _recompute(base, old) {
- const res = super._recompute(base, old);
- if (typeof base === 'string') {
- base = this.baseNameMap[base];
- }
- PosComponent.components[base.name] = res;
- return res;
+class ComponentRegistry extends ClassRegistry {
+ freeze() {
+ super.freeze();
+ // Make sure PosComponent has the compiled classes.
+ // This way, we don't need to explicitly declare that
+ // a set of components is children of another.
+ PosComponent.components = {};
+ for (const [base, compiledClass] of this.cache.entries()) {
+ PosComponent.components[base.name] = compiledClass;
}
}
+ _recompute(base, old) {
+ const res = super._recompute(base, old);
+ if (typeof base === "string") {
+ base = this.baseNameMap[base];
+ }
+ PosComponent.components[base.name] = res;
+ return res;
+ }
+}
- return ComponentRegistry;
-});
+export default ComponentRegistry;
diff --git a/addons/point_of_sale/static/src/js/ControlButtonsMixin.js b/addons/point_of_sale/static/src/js/ControlButtonsMixin.js
index 4d221b6dba1..1ed763ce552 100644
--- a/addons/point_of_sale/static/src/js/ControlButtonsMixin.js
+++ b/addons/point_of_sale/static/src/js/ControlButtonsMixin.js
@@ -1,107 +1,107 @@
-odoo.define('point_of_sale.ControlButtonsMixin', function (require) {
- 'use strict';
+/** @odoo-module */
- const Registries = require('point_of_sale.Registries');
+import Registries from "@point_of_sale/js/Registries";
+/**
+ * Component that has this mixin allows the use of `addControlButton`.
+ * All added control buttons that satisfies the condition can be accessed
+ * thru the `controlButtons` field of the Component's instance. These
+ * control buttons can then be rendered in the Component.
+ * @param {Function} x superclass
+ */
+const ControlButtonsMixin = (x) => {
+ const controlButtonsToPosition = [];
+ const sortedControlButtons = [];
+
+ class Extended extends x {
+ get controlButtons() {
+ return sortedControlButtons
+ .filter((cb) => {
+ return cb.condition ? cb.condition.bind(this)() : true;
+ })
+ .map((cb) =>
+ Object.assign({}, cb, { component: Registries.Component.get(cb.component) })
+ );
+ }
+ }
/**
- * Component that has this mixin allows the use of `addControlButton`.
- * All added control buttons that satisfies the condition can be accessed
- * thru the `controlButtons` field of the Component's instance. These
- * control buttons can then be rendered in the Component.
- * @param {Function} x superclass
+ * @param {Object} controlButton
+ * @param {Function} controlButton.component
+ * Base class that is added in the Registries.Component.
+ * @param {Function} controlButton.condition zero argument function that is bound
+ * to the instance of ProductScreen, such that `this.env.pos` can be used
+ * inside the function.
+ * @param {Array} [controlButton.position] array of two elements
+ * [locator, relativeTo]
+ * locator: string -> any of ('before', 'after', 'replace')
+ * relativeTo: string -> other controlButtons component name
*/
- const ControlButtonsMixin = (x) => {
- const controlButtonsToPosition = [];
- const sortedControlButtons = [];
-
- class Extended extends x {
- get controlButtons() {
- return sortedControlButtons
- .filter((cb) => {
- return cb.condition ? cb.condition.bind(this)() : true;
- })
- .map((cb) =>
- Object.assign({}, cb, { component: Registries.Component.get(cb.component) })
- );
- }
+ Extended.addControlButton = function (controlButton) {
+ // We set the name first.
+ if (!controlButton.name) {
+ controlButton.name = controlButton.component.name;
}
- /**
- * @param {Object} controlButton
- * @param {Function} controlButton.component
- * Base class that is added in the Registries.Component.
- * @param {Function} controlButton.condition zero argument function that is bound
- * to the instance of ProductScreen, such that `this.env.pos` can be used
- * inside the function.
- * @param {Array} [controlButton.position] array of two elements
- * [locator, relativeTo]
- * locator: string -> any of ('before', 'after', 'replace')
- * relativeTo: string -> other controlButtons component name
- */
- Extended.addControlButton = function (controlButton) {
- // We set the name first.
- if (!controlButton.name) {
- controlButton.name = controlButton.component.name;
- }
- // If no position is set, we just push it to the array.
- if (!controlButton.position) {
- sortedControlButtons.push(controlButton);
- } else {
- controlButtonsToPosition.push(controlButton);
- }
- };
-
- /**
- * Call this static method to make the added control buttons in proper
- * order.
- * NOTE: This isn't necessarily a fast algorithm. I doubt that the number
- * of control buttons will exceed an order of hundreds, so for practical
- * purposes, it is enough.
- */
- Extended.sortControlButtons = function () {
- function setControlButton(locator, index, cb) {
- if (locator == 'replace') {
- sortedControlButtons[index] = cb;
- } else if (locator == 'before') {
- sortedControlButtons.splice(index, 0, cb);
- } else if (locator == 'after') {
- sortedControlButtons.splice(index + 1, 0, cb);
- }
- }
- function locate(cb) {
- const [locator, reference] = cb.position;
- const index = sortedControlButtons.findIndex((cb) => cb.name == reference);
- return [locator, index, reference];
- }
- const cbMissingReference = [];
- // 1. First pass. If the reference control button isn't there, collect it for second pass.
- for (let cb of controlButtonsToPosition) {
- const [locator, index] = locate(cb);
- if (index == -1) {
- cbMissingReference.push(cb);
- continue;
- }
- setControlButton(locator, index, cb);
- }
- // 2. Second pass.
- // If during the first pass, 1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5 and 5 is already
- // in the sorted control buttons, then 1, 2, 3 & 4 are put in `cbMissingReference`.
- // This only means 2 things about the objects in `cbMissingReference`:
- // i) They are referencing the cb after them
- // ii) They really have missing reference.
- // Thus, we have to iterate the cb with missing reference in reverse.
- for (let cb of cbMissingReference.reverse()) {
- const [locator, index, reference] = locate(cb);
- if (index == -1) {
- console.warn(`'${cb.name}' is not properly position because '${reference}' is not found. Is '${reference}' spelled correctly?`);
- sortedControlButtons.push(cb);
- } else {
- setControlButton(locator, index, cb);
- }
- }
+ // If no position is set, we just push it to the array.
+ if (!controlButton.position) {
+ sortedControlButtons.push(controlButton);
+ } else {
+ controlButtonsToPosition.push(controlButton);
}
- return Extended;
};
- return ControlButtonsMixin;
-});
+ /**
+ * Call this static method to make the added control buttons in proper
+ * order.
+ * NOTE: This isn't necessarily a fast algorithm. I doubt that the number
+ * of control buttons will exceed an order of hundreds, so for practical
+ * purposes, it is enough.
+ */
+ Extended.sortControlButtons = function () {
+ function setControlButton(locator, index, cb) {
+ if (locator == "replace") {
+ sortedControlButtons[index] = cb;
+ } else if (locator == "before") {
+ sortedControlButtons.splice(index, 0, cb);
+ } else if (locator == "after") {
+ sortedControlButtons.splice(index + 1, 0, cb);
+ }
+ }
+ function locate(cb) {
+ const [locator, reference] = cb.position;
+ const index = sortedControlButtons.findIndex((cb) => cb.name == reference);
+ return [locator, index, reference];
+ }
+ const cbMissingReference = [];
+ // 1. First pass. If the reference control button isn't there, collect it for second pass.
+ for (const cb of controlButtonsToPosition) {
+ const [locator, index] = locate(cb);
+ if (index == -1) {
+ cbMissingReference.push(cb);
+ continue;
+ }
+ setControlButton(locator, index, cb);
+ }
+ // 2. Second pass.
+ // If during the first pass, 1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5 and 5 is already
+ // in the sorted control buttons, then 1, 2, 3 & 4 are put in `cbMissingReference`.
+ // This only means 2 things about the objects in `cbMissingReference`:
+ // i) They are referencing the cb after them
+ // ii) They really have missing reference.
+ // Thus, we have to iterate the cb with missing reference in reverse.
+ for (const cb of cbMissingReference.reverse()) {
+ const [locator, index, reference] = locate(cb);
+ if (index == -1) {
+ console.warn(
+ `'${cb.name}' is not properly position because '${reference}' is not found. Is '${reference}' spelled correctly?`
+ );
+ sortedControlButtons.push(cb);
+ } else {
+ setControlButton(locator, index, cb);
+ }
+ }
+ };
+ return Extended;
+};
+
+export default ControlButtonsMixin;
diff --git a/addons/point_of_sale/static/src/js/Gui.js b/addons/point_of_sale/static/src/js/Gui.js
index 6be8f41014a..f6d3c6806a4 100644
--- a/addons/point_of_sale/static/src/js/Gui.js
+++ b/addons/point_of_sale/static/src/js/Gui.js
@@ -1,64 +1,61 @@
-odoo.define('point_of_sale.Gui', function (require) {
- 'use strict';
+/** @odoo-module */
+const { status } = owl;
- const { status } = owl;
+/**
+ * This module bridges the data classes (such as those defined in
+ * models.js) to the view (Component) but not vice versa.
+ *
+ * The idea is to be able to perform side-effects to the user interface
+ * during calculation. Think of console.log during times we want to see
+ * the result of calculations. This is no different, except that instead
+ * of printing something in the console, we access a method in the user
+ * interface then the user interface reacts, e.g. calling `showPopup`.
+ *
+ * This however can be dangerous to the user interface as it can be possible
+ * that a rendered component is destroyed during the calculation. Because of
+ * this, we are going to limit external ui controls to those safe ones to
+ * use such as:
+ * - `showPopup`
+ * - `showTempScreen`
+ *
+ * IMPROVEMENT: After all, this Gui layer seems to be a good abstraction because
+ * there is a complete decoupling between data and view despite the data being
+ * able to use selected functionalities in the view layer. More formalized
+ * implementation is welcome.
+ */
- /**
- * This module bridges the data classes (such as those defined in
- * models.js) to the view (Component) but not vice versa.
- *
- * The idea is to be able to perform side-effects to the user interface
- * during calculation. Think of console.log during times we want to see
- * the result of calculations. This is no different, except that instead
- * of printing something in the console, we access a method in the user
- * interface then the user interface reacts, e.g. calling `showPopup`.
- *
- * This however can be dangerous to the user interface as it can be possible
- * that a rendered component is destroyed during the calculation. Because of
- * this, we are going to limit external ui controls to those safe ones to
- * use such as:
- * - `showPopup`
- * - `showTempScreen`
- *
- * IMPROVEMENT: After all, this Gui layer seems to be a good abstraction because
- * there is a complete decoupling between data and view despite the data being
- * able to use selected functionalities in the view layer. More formalized
- * implementation is welcome.
- */
+const config = {};
- const config = {};
+/**
+ * Call this when the user interface is ready. Provide the component
+ * that will be used to control the ui.
+ * @param {component} component component having the ui methods.
+ */
+export const configureGui = ({ component }) => {
+ config.component = component;
+ config.availableMethods = new Set([
+ "showScreen",
+ "showPopup",
+ "showTempScreen",
+ "playSound",
+ "setSyncStatus",
+ "showNotification",
+ ]);
+};
- /**
- * Call this when the user interface is ready. Provide the component
- * that will be used to control the ui.
- * @param {component} component component having the ui methods.
- */
- const configureGui = ({ component }) => {
- config.component = component;
- config.availableMethods = new Set([
- 'showScreen',
- 'showPopup',
- 'showTempScreen',
- 'playSound',
- 'setSyncStatus',
- 'showNotification',
- ]);
- };
-
- /**
- * Import this and consume like so: `Gui.showPopup(, )`.
- * Like you would call `showPopup` in a component.
- */
- const Gui = new Proxy(config, {
- get(target, key) {
- const { component, availableMethods } = target;
- if (!component) throw new Error(`Call 'configureGui' before using Gui.`);
- const isMounted = status(component) === 'mounted';
- if (availableMethods.has(key) && isMounted) {
- return component[key].bind(component);
- }
- },
- });
-
- return { configureGui, Gui };
+/**
+ * Import this and consume like so: `Gui.showPopup(, )`.
+ * Like you would call `showPopup` in a component.
+ */
+export const Gui = new Proxy(config, {
+ get(target, key) {
+ const { component, availableMethods } = target;
+ if (!component) {
+ throw new Error(`Call 'configureGui' before using Gui.`);
+ }
+ const isMounted = status(component) === "mounted";
+ if (availableMethods.has(key) && isMounted) {
+ return component[key].bind(component);
+ }
+ },
});
diff --git a/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js b/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js
index f88cff74d71..8355c7b449b 100644
--- a/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js
+++ b/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js
@@ -1,63 +1,63 @@
-odoo.define('point_of_sale.AbstractReceiptScreen', function (require) {
- 'use strict';
+/** @odoo-module */
- const { nextFrame } = require('point_of_sale.utils');
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { nextFrame } from "@point_of_sale/js/utils";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { useRef } = owl;
+const { useRef } = owl;
- /**
- * This relies on the assumption that there is a reference to
- * `order-receipt` so it is important to declare a `t-ref` to
- * `order-receipt` in the template of the Component that extends
- * this abstract component.
- */
- class AbstractReceiptScreen extends PosComponent {
- 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.innerHTML);
- if (printResult.successful) {
- return true;
- } else {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: printResult.message.title,
- body: 'Do you want to print using the web printer?',
- });
- if (confirmed) {
- // We want to call the _printWeb when the popup is fully gone
- // from the screen which happens after the next animation frame.
- await nextFrame();
- return await this._printWeb();
- }
- return false;
- }
- } else {
- return await this._printWeb();
- }
- }
- async _printWeb() {
- try {
- window.print();
+/**
+ * This relies on the assumption that there is a reference to
+ * `order-receipt` so it is important to declare a `t-ref` to
+ * `order-receipt` in the template of the Component that extends
+ * this abstract component.
+ */
+class AbstractReceiptScreen extends PosComponent {
+ 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.innerHTML
+ );
+ if (printResult.successful) {
return true;
- } catch {
- await this.showPopup('ErrorPopup', {
- title: this.env._t('Printing is not supported on some browsers'),
- body: this.env._t(
- 'Printing is not supported on some browsers due to no default printing protocol ' +
- 'is available. It is possible to print your tickets by making use of an IoT Box.'
- ),
+ } else {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: printResult.message.title,
+ body: "Do you want to print using the web printer?",
});
+ if (confirmed) {
+ // We want to call the _printWeb when the popup is fully gone
+ // from the screen which happens after the next animation frame.
+ await nextFrame();
+ return await this._printWeb();
+ }
return false;
}
+ } else {
+ return await this._printWeb();
}
}
+ async _printWeb() {
+ try {
+ window.print();
+ return true;
+ } catch {
+ await this.showPopup("ErrorPopup", {
+ title: this.env._t("Printing is not supported on some browsers"),
+ body: this.env._t(
+ "Printing is not supported on some browsers due to no default printing protocol " +
+ "is available. It is possible to print your tickets by making use of an IoT Box."
+ ),
+ });
+ return false;
+ }
+ }
+}
- Registries.Component.add(AbstractReceiptScreen);
+Registries.Component.add(AbstractReceiptScreen);
- return AbstractReceiptScreen;
-});
+export default AbstractReceiptScreen;
diff --git a/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js b/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js
index e7ced094b69..82481f61240 100644
--- a/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js
+++ b/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js
@@ -1,13 +1,11 @@
-odoo.define('point_of_sale.CurrencyAmount', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class CurrencyAmount extends PosComponent {}
- CurrencyAmount.template = 'CurrencyAmount';
+class CurrencyAmount extends PosComponent {}
+CurrencyAmount.template = "CurrencyAmount";
- Registries.Component.add(CurrencyAmount);
+Registries.Component.add(CurrencyAmount);
- return CurrencyAmount;
-});
+export default CurrencyAmount;
diff --git a/addons/point_of_sale/static/src/js/Misc/Draggable.js b/addons/point_of_sale/static/src/js/Misc/Draggable.js
index 751bbbee52f..82f4ef5fce8 100644
--- a/addons/point_of_sale/static/src/js/Misc/Draggable.js
+++ b/addons/point_of_sale/static/src/js/Misc/Draggable.js
@@ -1,145 +1,145 @@
-odoo.define('point_of_sale.Draggable', function(require) {
- 'use strict';
+/** @odoo-module */
- const { useListener } = require("@web/core/utils/hooks");
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { useListener } from "@web/core/utils/hooks";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { onMounted, useExternalListener } = owl;
+const { onMounted, useExternalListener } = owl;
- /**
- * Wrap an element or a component with { position: absolute } to make it
- * draggable around the limitArea or the nearest positioned ancestor.
- *
- * e.g.
- * ```
- *
- *
- *
- *
- *
- *
- *
- *
- * ```
- *
- * In the above snippet, if the popup div is { position: absolute },
- * then it becomes draggable around the .limit-area element if it is dragged
- * thru its Header -- because of the .drag-handle element.
- *
- * @trigger 'drag-end' when dragging ended with payload `{ loc: { top, left } }`
- */
- class Draggable extends PosComponent {
- setup() {
- super.setup();
- this.isDragging = false;
- this.dx = 0;
- this.dy = 0;
- // drag with mouse
- useExternalListener(document, 'mousemove', this.move);
- useExternalListener(document, 'mouseup', this.endDrag);
- // drag with touch
- useExternalListener(document, 'touchmove', this.move);
- useExternalListener(document, 'touchend', this.endDrag);
+/**
+ * Wrap an element or a component with { position: absolute } to make it
+ * draggable around the limitArea or the nearest positioned ancestor.
+ *
+ * e.g.
+ * ```
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * In the above snippet, if the popup div is { position: absolute },
+ * then it becomes draggable around the .limit-area element if it is dragged
+ * thru its Header -- because of the .drag-handle element.
+ *
+ * @trigger 'drag-end' when dragging ended with payload `{ loc: { top, left } }`
+ */
+class Draggable extends PosComponent {
+ setup() {
+ super.setup();
+ this.isDragging = false;
+ this.dx = 0;
+ this.dy = 0;
+ // drag with mouse
+ useExternalListener(document, "mousemove", this.move);
+ useExternalListener(document, "mouseup", this.endDrag);
+ // drag with touch
+ useExternalListener(document, "touchmove", this.move);
+ useExternalListener(document, "touchend", this.endDrag);
- useListener('mousedown', '.drag-handle', this.startDrag);
- useListener('touchstart', '.drag-handle', this.startDrag);
+ useListener("mousedown", ".drag-handle", this.startDrag);
+ useListener("touchstart", ".drag-handle", this.startDrag);
- onMounted(() => {
- this.limitArea = this.props.limitArea
- ? document.querySelector(this.props.limitArea)
- : this.el.offsetParent;
- if (!this.limitArea) return;
- 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;
- if (event instanceof CustomEvent) {
- realEvent = event.detail;
+ onMounted(() => {
+ this.limitArea = this.props.limitArea
+ ? document.querySelector(this.props.limitArea)
+ : this.el.offsetParent;
+ if (!this.limitArea) {
+ return;
+ }
+ 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 {
- realEvent = event;
+ 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;
}
- const { x, y } = this._getEventLoc(realEvent);
- this.isDragging = true;
- this.dx = this.el.offsetLeft - x;
- this.dy = this.el.offsetTop - y;
- event.stopPropagation();
+ 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;
+ if (event instanceof CustomEvent) {
+ realEvent = event.detail;
+ } else {
+ realEvent = event;
}
- move(event) {
- if (this.isDragging) {
- const { x: pointerX, y: pointerY } = this._getEventLoc(event);
- const posLeft = this._getPosLeft(pointerX, this.dx);
- const posTop = this._getPosTop(pointerY, this.dy);
- this.el.style.left = `${posLeft}px`;
- this.el.style.top = `${posTop}px`;
- }
- }
- endDrag() {
- if (this.isDragging) {
- this.isDragging = false;
- this.trigger('drag-end', {
- loc: { top: this.el.offsetTop, left: this.el.offsetLeft },
- });
- }
- }
- _getEventLoc(event) {
- let coordX, coordY;
- if (event.touches && event.touches[0]) {
- coordX = event.touches[0].clientX;
- coordY = event.touches[0].clientY;
- } else {
- coordX = event.clientX;
- coordY = event.clientY;
- }
- return {
- x: coordX,
- y: coordY,
- };
- }
- _getPosLeft(pointerX, dx) {
- const posLeft = pointerX + dx;
- if (posLeft < this.limitLeft) {
- return this.limitLeft;
- } else if (posLeft > this.limitRight - this.el.offsetWidth) {
- return this.limitRight - this.el.offsetWidth;
- }
- return posLeft;
- }
- _getPosTop(pointerY, dy) {
- const posTop = pointerY + dy;
- if (posTop < this.limitTop) {
- return this.limitTop;
- } else if (posTop > this.limitBottom - this.el.offsetHeight) {
- return this.limitBottom - this.el.offsetHeight;
- }
- return posTop;
+ const { x, y } = this._getEventLoc(realEvent);
+ this.isDragging = true;
+ this.dx = this.el.offsetLeft - x;
+ this.dy = this.el.offsetTop - y;
+ event.stopPropagation();
+ }
+ move(event) {
+ if (this.isDragging) {
+ const { x: pointerX, y: pointerY } = this._getEventLoc(event);
+ const posLeft = this._getPosLeft(pointerX, this.dx);
+ const posTop = this._getPosTop(pointerY, this.dy);
+ this.el.style.left = `${posLeft}px`;
+ this.el.style.top = `${posTop}px`;
}
}
- Draggable.template = 'Draggable';
+ endDrag() {
+ if (this.isDragging) {
+ this.isDragging = false;
+ this.trigger("drag-end", {
+ loc: { top: this.el.offsetTop, left: this.el.offsetLeft },
+ });
+ }
+ }
+ _getEventLoc(event) {
+ let coordX, coordY;
+ if (event.touches && event.touches[0]) {
+ coordX = event.touches[0].clientX;
+ coordY = event.touches[0].clientY;
+ } else {
+ coordX = event.clientX;
+ coordY = event.clientY;
+ }
+ return {
+ x: coordX,
+ y: coordY,
+ };
+ }
+ _getPosLeft(pointerX, dx) {
+ const posLeft = pointerX + dx;
+ if (posLeft < this.limitLeft) {
+ return this.limitLeft;
+ } else if (posLeft > this.limitRight - this.el.offsetWidth) {
+ return this.limitRight - this.el.offsetWidth;
+ }
+ return posLeft;
+ }
+ _getPosTop(pointerY, dy) {
+ const posTop = pointerY + dy;
+ if (posTop < this.limitTop) {
+ return this.limitTop;
+ } else if (posTop > this.limitBottom - this.el.offsetHeight) {
+ return this.limitBottom - this.el.offsetHeight;
+ }
+ return posTop;
+ }
+}
+Draggable.template = "Draggable";
- Registries.Component.add(Draggable);
+Registries.Component.add(Draggable);
- return Draggable;
-});
+export default Draggable;
diff --git a/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js b/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js
index 81640e2f5aa..c46d93f7294 100644
--- a/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js
+++ b/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js
@@ -1,15 +1,13 @@
-odoo.define('point_of_sale.IndependentToOrderScreen', function (require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
+import PosComponent from "@point_of_sale/js/PosComponent";
- class IndependentToOrderScreen extends PosComponent {
- close() {
- const order = this.env.pos.get_order();
- const { name: screenName } = order.get_screen_data();
- this.showScreen(screenName);
- }
+class IndependentToOrderScreen extends PosComponent {
+ close() {
+ const order = this.env.pos.get_order();
+ const { name: screenName } = order.get_screen_data();
+ this.showScreen(screenName);
}
+}
- return IndependentToOrderScreen;
-});
+export default IndependentToOrderScreen;
diff --git a/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js b/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js
index ffc3424e564..ddbb2817fb0 100644
--- a/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js
+++ b/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js
@@ -1,25 +1,25 @@
-odoo.define('point_of_sale.MobileOrderWidget', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class MobileOrderWidget extends PosComponent {
- get order() {
- return this.env.pos.get_order();
- }
- get total() {
- const _total = this.order ? this.order.get_total_with_tax() : 0;
- return this.env.pos.format_currency(_total);
- }
- get items_number() {
- return this.order ? this.order.orderlines.reduce((items_number,line) => items_number + line.quantity, 0) : 0;
- }
+class MobileOrderWidget extends PosComponent {
+ get order() {
+ return this.env.pos.get_order();
}
+ get total() {
+ const _total = this.order ? this.order.get_total_with_tax() : 0;
+ return this.env.pos.format_currency(_total);
+ }
+ get items_number() {
+ return this.order
+ ? this.order.orderlines.reduce((items_number, line) => items_number + line.quantity, 0)
+ : 0;
+ }
+}
- MobileOrderWidget.template = 'MobileOrderWidget';
+MobileOrderWidget.template = "MobileOrderWidget";
- Registries.Component.add(MobileOrderWidget);
+Registries.Component.add(MobileOrderWidget);
- return MobileOrderWidget;
-});
+export default MobileOrderWidget;
diff --git a/addons/point_of_sale/static/src/js/Misc/NotificationSound.js b/addons/point_of_sale/static/src/js/Misc/NotificationSound.js
index e783225e38b..3132dc2b468 100644
--- a/addons/point_of_sale/static/src/js/Misc/NotificationSound.js
+++ b/addons/point_of_sale/static/src/js/Misc/NotificationSound.js
@@ -1,19 +1,17 @@
-odoo.define('point_of_sale.NotificationSound', function (require) {
- 'use strict';
+/** @odoo-module */
- const { useListener } = require("@web/core/utils/hooks");
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { useListener } from "@web/core/utils/hooks";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class NotificationSound extends PosComponent {
- setup() {
- super.setup();
- useListener('ended', () => (this.props.sound.src = null));
- }
+class NotificationSound extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("ended", () => (this.props.sound.src = null));
}
- NotificationSound.template = 'NotificationSound';
+}
+NotificationSound.template = "NotificationSound";
- Registries.Component.add(NotificationSound);
+Registries.Component.add(NotificationSound);
- return NotificationSound;
-});
+export default NotificationSound;
diff --git a/addons/point_of_sale/static/src/js/Misc/NumberBuffer.js b/addons/point_of_sale/static/src/js/Misc/NumberBuffer.js
index c1a87844a44..ff3e5736ae6 100644
--- a/addons/point_of_sale/static/src/js/Misc/NumberBuffer.js
+++ b/addons/point_of_sale/static/src/js/Misc/NumberBuffer.js
@@ -1,311 +1,320 @@
-odoo.define('point_of_sale.NumberBuffer', function(require) {
- 'use strict';
+/** @odoo-module */
- const { useListener } = require("@web/core/utils/hooks");
- const { parse } = require('web.field_utils');
- const { barcodeService } = require('@barcodes/barcode_service');
- const { _t } = require('web.core');
- const { Gui } = require('point_of_sale.Gui');
+import { useListener } from "@web/core/utils/hooks";
+import { parse } from "web.field_utils";
+import { barcodeService } from "@barcodes/barcode_service";
+import { _t } from "web.core";
+import { Gui } from "@point_of_sale/js/Gui";
- const { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } = owl;
- const INPUT_KEYS = new Set(
- ['Delete', 'Backspace', '+1', '+2', '+5', '+10', '+20', '+50'].concat('0123456789+-.,'.split(''))
- );
- const CONTROL_KEYS = new Set(['Enter', 'Esc']);
- const ALLOWED_KEYS = new Set([...INPUT_KEYS, ...CONTROL_KEYS]);
- const getDefaultConfig = () => ({
- decimalPoint: false,
- triggerAtEnter: false,
- triggerAtEsc: false,
- triggerAtInput: false,
- nonKeyboardInputEvent: false,
- useWithBarcode: false,
- });
+const { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } = owl;
+const INPUT_KEYS = new Set(
+ ["Delete", "Backspace", "+1", "+2", "+5", "+10", "+20", "+50"].concat(
+ "0123456789+-.,".split("")
+ )
+);
+const CONTROL_KEYS = new Set(["Enter", "Esc"]);
+const ALLOWED_KEYS = new Set([...INPUT_KEYS, ...CONTROL_KEYS]);
+const getDefaultConfig = () => ({
+ decimalPoint: false,
+ triggerAtEnter: false,
+ triggerAtEsc: false,
+ triggerAtInput: false,
+ nonKeyboardInputEvent: false,
+ useWithBarcode: false,
+});
+/**
+ * This is a singleton.
+ *
+ * Only one component can `use` the buffer at a time.
+ * This is done by keeping track of each component (and its
+ * corresponding state and config) using a stack (bufferHolderStack).
+ * The component on top of the stack is the one that currently
+ * `holds` the buffer.
+ *
+ * When the current component is unmounted, the top of the stack
+ * is popped and NumberBuffer is set up again for the new component
+ * on top of the stack.
+ *
+ * Usage
+ * =====
+ * - Activate in the construction of root component. `NumberBuffer.activate()`
+ * - Use the buffer in a child component by calling `NumberBuffer.use()`
+ * in the constructor of the child component.
+ * - The component that `uses` the buffer has access to the following instance
+ * methods of the NumberBuffer:
+ * - get()
+ * - set(val)
+ * - reset()
+ * - getFloat()
+ * - capture()
+ *
+ * Note
+ * ====
+ * - No need to instantiate as it is a singleton created before exporting in this module.
+ *
+ * Possible Improvements
+ * =====================
+ * - Relieve the buffer from responsibility of handling `Enter` and other control keys.
+ * - Make the constants (ALLOWED_KEYS, etc.) more configurable.
+ * - Write more integration tests. NumberPopup can be used as test component.
+ */
+class NumberBuffer extends EventBus {
+ constructor() {
+ super();
+ this.isReset = false;
+ this.bufferHolderStack = [];
+ }
/**
- * This is a singleton.
- *
- * Only one component can `use` the buffer at a time.
- * This is done by keeping track of each component (and its
- * corresponding state and config) using a stack (bufferHolderStack).
- * The component on top of the stack is the one that currently
- * `holds` the buffer.
- *
- * When the current component is unmounted, the top of the stack
- * is popped and NumberBuffer is set up again for the new component
- * on top of the stack.
- *
- * Usage
- * =====
- * - Activate in the construction of root component. `NumberBuffer.activate()`
- * - Use the buffer in a child component by calling `NumberBuffer.use()`
- * in the constructor of the child component.
- * - The component that `uses` the buffer has access to the following instance
- * methods of the NumberBuffer:
- * - get()
- * - set(val)
- * - reset()
- * - getFloat()
- * - capture()
- *
- * Note
- * ====
- * - No need to instantiate as it is a singleton created before exporting in this module.
- *
- * Possible Improvements
- * =====================
- * - Relieve the buffer from responsibility of handling `Enter` and other control keys.
- * - Make the constants (ALLOWED_KEYS, etc.) more configurable.
- * - Write more integration tests. NumberPopup can be used as test component.
+ * @returns {String} value of the buffer, e.g. '-95.79'
*/
- class NumberBuffer extends EventBus {
- constructor() {
- super();
- this.isReset = false;
- this.bufferHolderStack = [];
- }
- /**
- * @returns {String} value of the buffer, e.g. '-95.79'
- */
- get() {
- return this.state ? this.state.buffer : null;
- }
- /**
- * Takes a string that is convertible to float, and set it as
- * value of the buffer. e.g. val = '2.99';
- *
- * @param {String} val
- */
- set(val) {
- this.state.buffer = !isNaN(parseFloat(val)) ? val : '';
- this.trigger('buffer-update', this.state.buffer);
- }
- /**
- * Resets the buffer to empty string.
- */
- reset() {
- this.isReset = true;
- this.state.buffer = '';
- this.trigger('buffer-update', this.state.buffer);
- }
- /**
- * Calling this function, we immediately invoke the `handler` method
- * that handles the contents of the input events buffer (`eventsBuffer`).
- * This is helpful when we don't want to wait for the timeout that
- * is supposed to invoke the handler.
- */
- capture() {
- if (this.handler) {
- clearTimeout(this._timeout);
- this.handler();
- delete this.handler;
- }
- }
- /**
- * @returns {number} float equivalent of the value of buffer
- */
- getFloat() {
- return parse.float(this.get());
- }
- /**
- * Add keyup listener to window via the useExternalListener hook.
- * When the component calling this is unmounted, the listener is also
- * removed from window.
- */
- activate() {
- this.defaultDecimalPoint = _t.database.parameters.decimal_point;
- useExternalListener(window, 'keyup', this._onKeyboardInput.bind(this));
- }
- /**
- * @param {Object} config Use to setup the buffer
- * @param {String|null} config.decimalPoint The decimal character.
- * @param {String|null} config.triggerAtEnter Event triggered when 'Enter' key is pressed.
- * @param {String|null} config.triggerAtEsc Event triggered when 'Esc' key is pressed.
- * @param {String|null} config.triggerAtInput Event triggered for every accepted input.
- * @param {String|null} config.nonKeyboardInputEvent Also listen to a non-keyboard input event
- * that carries a payload of { key }. The key is checked if it is a valid input. If valid,
- * the number buffer is modified just as it is modified when a keyboard key is pressed.
- * @param {Boolean} config.useWithBarcode Whether this buffer is used with barcode.
- * @emits config.triggerAtEnter when 'Enter' key is pressed.
- * @emits config.triggerAtEsc when 'Esc' key is pressed.
- * @emits config.triggerAtInput when an input is accepted.
- */
- use(config) {
- this.eventsBuffer = [];
- const currentComponent = useComponent();
- config = Object.assign(getDefaultConfig(), config);
- onMounted(() => {
- this.bufferHolderStack.push({
- component: currentComponent,
- state: config.state ? config.state : { buffer: '', toStartOver: false },
- config,
- });
- this._setUp();
- });
- onWillUnmount(() => {
- this.bufferHolderStack.pop();
- this._setUp();
- });
- // Add listener that accepts non keyboard inputs
- if (typeof config.nonKeyboardInputEvent === 'string') {
- useListener(config.nonKeyboardInputEvent, this._onNonKeyboardInput.bind(this));
- }
- }
- get _currentBufferHolder() {
- return this.bufferHolderStack[this.bufferHolderStack.length - 1];
- }
- _setUp() {
- if (!this._currentBufferHolder) return;
- const { component, state, config } = this._currentBufferHolder;
- this.component = component;
- this.state = state;
- this.config = config;
- this.decimalPoint = config.decimalPoint || this.defaultDecimalPoint;
- this.maxTimeBetweenKeys = this.config.useWithBarcode
- ? barcodeService.maxTimeBetweenKeysInMs
- : 0;
- }
- _onKeyboardInput(event) {
- return this._bufferEvents(this._onInput(event => event.key))(event);
- }
- _onNonKeyboardInput(event) {
- return this._bufferEvents(this._onInput(event => event.detail.key))(event);
- }
- _bufferEvents(handler) {
- return event => {
- if (['INPUT', 'TEXTAREA'].includes(event.target.tagName) || !this.eventsBuffer) return;
- clearTimeout(this._timeout);
- this.eventsBuffer.push(event);
- this._timeout = setTimeout(handler, this.maxTimeBetweenKeys);
- this.handler = handler
- };
- }
- _onInput(keyAccessor) {
- return () => {
- if (this.eventsBuffer.length <= 2) {
- // Check first the buffer if its contents are all valid
- // number input.
- for (let event of this.eventsBuffer) {
- if (!ALLOWED_KEYS.has(keyAccessor(event))) {
- this.eventsBuffer = [];
- return;
- }
- }
- // At this point, all the events in buffer
- // contains number input. It's now okay to handle
- // each input.
- for (let event of this.eventsBuffer) {
- this._handleInput(keyAccessor(event));
- event.preventDefault();
- event.stopPropagation();
- }
- }
- this.eventsBuffer = [];
- };
- }
- _handleInput(key) {
- if (key === 'Enter' && this.config.triggerAtEnter) {
- this.component.trigger(this.config.triggerAtEnter, this.state);
- } else if (key === 'Esc' && this.config.triggerAtEsc) {
- this.component.trigger(this.config.triggerAtEsc, this.state);
- } else if (INPUT_KEYS.has(key)) {
- this._updateBuffer(key);
- if (this.config.triggerAtInput)
- this.component.trigger(this.config.triggerAtInput, { buffer: this.state.buffer, key });
- }
- }
- /**
- * Updates the current buffer state using the given input.
- * @param {String} input valid input
- */
- _updateBuffer(input) {
- const isEmpty = val => {
- return val === '' || val === null;
- };
- if (input === undefined || input === null) return;
- let isFirstInput = isEmpty(this.state.buffer);
- if (input === ',' || input === '.') {
- if (this.state.toStartOver) {
- this.state.buffer = '';
- }
- if (isFirstInput) {
- this.state.buffer = '0' + this.decimalPoint;
- } else if (!this.state.buffer.length || this.state.buffer === '-') {
- this.state.buffer += '0' + this.decimalPoint;
- } else if (this.state.buffer.indexOf(this.decimalPoint) < 0) {
- this.state.buffer = this.state.buffer + this.decimalPoint;
- }
- } else if (input === 'Delete') {
- if (this.isReset) {
- this.state.buffer = '';
- this.isReset = false;
- return;
- }
- this.state.buffer = isEmpty(this.state.buffer) ? null : '';
- } else if (input === 'Backspace') {
- if (this.isReset) {
- this.state.buffer = '';
- this.isReset = false;
- return;
- }
- if (this.state.toStartOver) {
- this.state.buffer = '';
- }
- const buffer = this.state.buffer;
- if (isEmpty(buffer)) {
- this.state.buffer = null;
- } else {
- const nCharToRemove = buffer[buffer.length - 1] === this.decimalPoint ? 2 : 1;
- this.state.buffer = buffer.substring(0, buffer.length - nCharToRemove);
- }
- } else if (input === '+') {
- if (this.state.buffer[0] === '-') {
- this.state.buffer = this.state.buffer.substring(1, this.state.buffer.length);
- }
- } else if (input === '-') {
- if (isFirstInput) {
- this.state.buffer = '-0';
- } else if (this.state.buffer[0] === '-') {
- this.state.buffer = this.state.buffer.substring(1, this.state.buffer.length);
- } else {
- this.state.buffer = '-' + this.state.buffer;
- }
- } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
- // when input is like '+10', '+50', etc
- const inputValue = parse.float(input.slice(1));
- const currentBufferValue = this.state.buffer ? parse.float(this.state.buffer) : 0;
- this.state.buffer = this.component.env.pos.formatFixed(
- inputValue + currentBufferValue
- );
- } else if (!isNaN(parseInt(input, 10))) {
- if (this.state.toStartOver) { // when we want to erase the current buffer for a new value
- this.state.buffer = '';
- }
- if (isFirstInput) {
- this.state.buffer = '' + input;
- } else if (this.state.buffer.length > 12) {
- Gui.playSound('bell');
- } else {
- this.state.buffer += input;
- }
- }
- if (this.state.buffer === '-') {
- this.state.buffer = '';
- }
- // once an input is accepted and updated the buffer,
- // the buffer should not be in reset state anymore.
- this.isReset = false;
- // it should not be in a start the buffer over state anymore.
- this.state.toStartOver = false;
-
- if (this.config.maxValue && this.state.buffer > this.config.maxValue) {
- this.state.buffer = this.config.maxValue.toString();
- this.config.maxValueReached();
- }
-
- this.trigger('buffer-update', this.state.buffer);
+ get() {
+ return this.state ? this.state.buffer : null;
+ }
+ /**
+ * Takes a string that is convertible to float, and set it as
+ * value of the buffer. e.g. val = '2.99';
+ *
+ * @param {String} val
+ */
+ set(val) {
+ this.state.buffer = !isNaN(parseFloat(val)) ? val : "";
+ this.trigger("buffer-update", this.state.buffer);
+ }
+ /**
+ * Resets the buffer to empty string.
+ */
+ reset() {
+ this.isReset = true;
+ this.state.buffer = "";
+ this.trigger("buffer-update", this.state.buffer);
+ }
+ /**
+ * Calling this function, we immediately invoke the `handler` method
+ * that handles the contents of the input events buffer (`eventsBuffer`).
+ * This is helpful when we don't want to wait for the timeout that
+ * is supposed to invoke the handler.
+ */
+ capture() {
+ if (this.handler) {
+ clearTimeout(this._timeout);
+ this.handler();
+ delete this.handler;
}
}
+ /**
+ * @returns {number} float equivalent of the value of buffer
+ */
+ getFloat() {
+ return parse.float(this.get());
+ }
+ /**
+ * Add keyup listener to window via the useExternalListener hook.
+ * When the component calling this is unmounted, the listener is also
+ * removed from window.
+ */
+ activate() {
+ this.defaultDecimalPoint = _t.database.parameters.decimal_point;
+ useExternalListener(window, "keyup", this._onKeyboardInput.bind(this));
+ }
+ /**
+ * @param {Object} config Use to setup the buffer
+ * @param {String|null} config.decimalPoint The decimal character.
+ * @param {String|null} config.triggerAtEnter Event triggered when 'Enter' key is pressed.
+ * @param {String|null} config.triggerAtEsc Event triggered when 'Esc' key is pressed.
+ * @param {String|null} config.triggerAtInput Event triggered for every accepted input.
+ * @param {String|null} config.nonKeyboardInputEvent Also listen to a non-keyboard input event
+ * that carries a payload of { key }. The key is checked if it is a valid input. If valid,
+ * the number buffer is modified just as it is modified when a keyboard key is pressed.
+ * @param {Boolean} config.useWithBarcode Whether this buffer is used with barcode.
+ * @emits config.triggerAtEnter when 'Enter' key is pressed.
+ * @emits config.triggerAtEsc when 'Esc' key is pressed.
+ * @emits config.triggerAtInput when an input is accepted.
+ */
+ use(config) {
+ this.eventsBuffer = [];
+ const currentComponent = useComponent();
+ config = Object.assign(getDefaultConfig(), config);
+ onMounted(() => {
+ this.bufferHolderStack.push({
+ component: currentComponent,
+ state: config.state ? config.state : { buffer: "", toStartOver: false },
+ config,
+ });
+ this._setUp();
+ });
+ onWillUnmount(() => {
+ this.bufferHolderStack.pop();
+ this._setUp();
+ });
+ // Add listener that accepts non keyboard inputs
+ if (typeof config.nonKeyboardInputEvent === "string") {
+ useListener(config.nonKeyboardInputEvent, this._onNonKeyboardInput.bind(this));
+ }
+ }
+ get _currentBufferHolder() {
+ return this.bufferHolderStack[this.bufferHolderStack.length - 1];
+ }
+ _setUp() {
+ if (!this._currentBufferHolder) {
+ return;
+ }
+ const { component, state, config } = this._currentBufferHolder;
+ this.component = component;
+ this.state = state;
+ this.config = config;
+ this.decimalPoint = config.decimalPoint || this.defaultDecimalPoint;
+ this.maxTimeBetweenKeys = this.config.useWithBarcode
+ ? barcodeService.maxTimeBetweenKeysInMs
+ : 0;
+ }
+ _onKeyboardInput(event) {
+ return this._bufferEvents(this._onInput((event) => event.key))(event);
+ }
+ _onNonKeyboardInput(event) {
+ return this._bufferEvents(this._onInput((event) => event.detail.key))(event);
+ }
+ _bufferEvents(handler) {
+ return (event) => {
+ if (["INPUT", "TEXTAREA"].includes(event.target.tagName) || !this.eventsBuffer) {
+ return;
+ }
+ clearTimeout(this._timeout);
+ this.eventsBuffer.push(event);
+ this._timeout = setTimeout(handler, this.maxTimeBetweenKeys);
+ this.handler = handler;
+ };
+ }
+ _onInput(keyAccessor) {
+ return () => {
+ if (this.eventsBuffer.length <= 2) {
+ // Check first the buffer if its contents are all valid
+ // number input.
+ for (const event of this.eventsBuffer) {
+ if (!ALLOWED_KEYS.has(keyAccessor(event))) {
+ this.eventsBuffer = [];
+ return;
+ }
+ }
+ // At this point, all the events in buffer
+ // contains number input. It's now okay to handle
+ // each input.
+ for (const event of this.eventsBuffer) {
+ this._handleInput(keyAccessor(event));
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ this.eventsBuffer = [];
+ };
+ }
+ _handleInput(key) {
+ if (key === "Enter" && this.config.triggerAtEnter) {
+ this.component.trigger(this.config.triggerAtEnter, this.state);
+ } else if (key === "Esc" && this.config.triggerAtEsc) {
+ this.component.trigger(this.config.triggerAtEsc, this.state);
+ } else if (INPUT_KEYS.has(key)) {
+ this._updateBuffer(key);
+ if (this.config.triggerAtInput) {
+ this.component.trigger(this.config.triggerAtInput, {
+ buffer: this.state.buffer,
+ key,
+ });
+ }
+ }
+ }
+ /**
+ * Updates the current buffer state using the given input.
+ * @param {String} input valid input
+ */
+ _updateBuffer(input) {
+ const isEmpty = (val) => {
+ return val === "" || val === null;
+ };
+ if (input === undefined || input === null) {
+ return;
+ }
+ const isFirstInput = isEmpty(this.state.buffer);
+ if (input === "," || input === ".") {
+ if (this.state.toStartOver) {
+ this.state.buffer = "";
+ }
+ if (isFirstInput) {
+ this.state.buffer = "0" + this.decimalPoint;
+ } else if (!this.state.buffer.length || this.state.buffer === "-") {
+ this.state.buffer += "0" + this.decimalPoint;
+ } else if (this.state.buffer.indexOf(this.decimalPoint) < 0) {
+ this.state.buffer = this.state.buffer + this.decimalPoint;
+ }
+ } else if (input === "Delete") {
+ if (this.isReset) {
+ this.state.buffer = "";
+ this.isReset = false;
+ return;
+ }
+ this.state.buffer = isEmpty(this.state.buffer) ? null : "";
+ } else if (input === "Backspace") {
+ if (this.isReset) {
+ this.state.buffer = "";
+ this.isReset = false;
+ return;
+ }
+ if (this.state.toStartOver) {
+ this.state.buffer = "";
+ }
+ const buffer = this.state.buffer;
+ if (isEmpty(buffer)) {
+ this.state.buffer = null;
+ } else {
+ const nCharToRemove = buffer[buffer.length - 1] === this.decimalPoint ? 2 : 1;
+ this.state.buffer = buffer.substring(0, buffer.length - nCharToRemove);
+ }
+ } else if (input === "+") {
+ if (this.state.buffer[0] === "-") {
+ this.state.buffer = this.state.buffer.substring(1, this.state.buffer.length);
+ }
+ } else if (input === "-") {
+ if (isFirstInput) {
+ this.state.buffer = "-0";
+ } else if (this.state.buffer[0] === "-") {
+ this.state.buffer = this.state.buffer.substring(1, this.state.buffer.length);
+ } else {
+ this.state.buffer = "-" + this.state.buffer;
+ }
+ } else if (input[0] === "+" && !isNaN(parseFloat(input))) {
+ // when input is like '+10', '+50', etc
+ const inputValue = parse.float(input.slice(1));
+ const currentBufferValue = this.state.buffer ? parse.float(this.state.buffer) : 0;
+ this.state.buffer = this.component.env.pos.formatFixed(inputValue + currentBufferValue);
+ } else if (!isNaN(parseInt(input, 10))) {
+ if (this.state.toStartOver) {
+ // when we want to erase the current buffer for a new value
+ this.state.buffer = "";
+ }
+ if (isFirstInput) {
+ this.state.buffer = "" + input;
+ } else if (this.state.buffer.length > 12) {
+ Gui.playSound("bell");
+ } else {
+ this.state.buffer += input;
+ }
+ }
+ if (this.state.buffer === "-") {
+ this.state.buffer = "";
+ }
+ // once an input is accepted and updated the buffer,
+ // the buffer should not be in reset state anymore.
+ this.isReset = false;
+ // it should not be in a start the buffer over state anymore.
+ this.state.toStartOver = false;
- return new NumberBuffer();
-});
+ if (this.config.maxValue && this.state.buffer > this.config.maxValue) {
+ this.state.buffer = this.config.maxValue.toString();
+ this.config.maxValueReached();
+ }
+
+ this.trigger("buffer-update", this.state.buffer);
+ }
+}
+
+export default new NumberBuffer();
diff --git a/addons/point_of_sale/static/src/js/Misc/SearchBar.js b/addons/point_of_sale/static/src/js/Misc/SearchBar.js
index 09fd599ade0..4f74637dcd0 100644
--- a/addons/point_of_sale/static/src/js/Misc/SearchBar.js
+++ b/addons/point_of_sale/static/src/js/Misc/SearchBar.js
@@ -1,110 +1,114 @@
-odoo.define('point_of_sale.SearchBar', function (require) {
- 'use strict';
+/** @odoo-module */
- const { useAutofocus, useListener } = require("@web/core/utils/hooks");
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { useAutofocus, useListener } from "@web/core/utils/hooks";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { useExternalListener, useState } = owl;
+const { useExternalListener, useState } = owl;
+/**
+ * This is a simple configurable search bar component. It has search fields
+ * and selection filter. Search fields allow the users to specify the type
+ * of their searches. The filter is a dropdown menu for selection. Depending on
+ * user's action, this component emits corresponding event with the action
+ * information (payload).
+ *
+ * TODO: This component can be made more generic and be able to replace
+ * all the search bars across pos ui.
+ *
+ * @prop {{
+ * config: {
+ * searchFields: Map,
+ * filter: { show: boolean, options: Map }
+ * },
+ * placeholder: string,
+ * }}
+ * @emits search @payload { fieldName: string, searchTerm: '' }
+ * @emits filter-selected @payload { filter: string }
+ *
+ * NOTE: The payload of the emitted event is accessible via the `detail`
+ * field of the event.
+ */
+class SearchBar extends PosComponent {
+ setup() {
+ super.setup();
+ useAutofocus();
+ useExternalListener(window, "click", this._hideOptions);
+ useListener("click-search-field", this._onClickSearchField);
+ useListener("select-filter", this._onSelectFilter);
+ this.filterOptionsList = [...this.props.config.filter.options.keys()];
+ this.searchFieldsList = [...this.props.config.searchFields.keys()];
+ const defaultSearchFieldId = this.searchFieldsList.indexOf(
+ this.props.config.defaultSearchDetails.fieldName
+ );
+ this.state = useState({
+ searchInput: this.props.config.defaultSearchDetails.searchTerm || "",
+ selectedSearchFieldId: defaultSearchFieldId == -1 ? 0 : defaultSearchFieldId,
+ showSearchFields: false,
+ showFilterOptions: false,
+ selectedFilter: this.props.config.defaultFilter || this.filterOptionsList[0],
+ });
+ }
+ _onSelectFilter({ detail: key }) {
+ this.state.selectedFilter = key;
+ this.trigger("filter-selected", { filter: this.state.selectedFilter });
+ }
/**
- * This is a simple configurable search bar component. It has search fields
- * and selection filter. Search fields allow the users to specify the type
- * of their searches. The filter is a dropdown menu for selection. Depending on
- * user's action, this component emits corresponding event with the action
- * information (payload).
- *
- * TODO: This component can be made more generic and be able to replace
- * all the search bars across pos ui.
- *
- * @prop {{
- * config: {
- * searchFields: Map,
- * filter: { show: boolean, options: Map }
- * },
- * placeholder: string,
- * }}
- * @emits search @payload { fieldName: string, searchTerm: '' }
- * @emits filter-selected @payload { filter: string }
- *
- * NOTE: The payload of the emitted event is accessible via the `detail`
- * field of the event.
+ * When pressing vertical arrow keys, do not move the input cursor.
*/
- class SearchBar extends PosComponent {
- setup() {
- super.setup();
- useAutofocus();
- useExternalListener(window, 'click', this._hideOptions);
- useListener('click-search-field', this._onClickSearchField);
- useListener('select-filter', this._onSelectFilter);
- this.filterOptionsList = [...this.props.config.filter.options.keys()];
- this.searchFieldsList = [...this.props.config.searchFields.keys()];
- const defaultSearchFieldId = this.searchFieldsList.indexOf(
- this.props.config.defaultSearchDetails.fieldName
- );
- this.state = useState({
- searchInput: this.props.config.defaultSearchDetails.searchTerm || '',
- selectedSearchFieldId: defaultSearchFieldId == -1 ? 0 : defaultSearchFieldId,
- showSearchFields: false,
- showFilterOptions: false,
- selectedFilter: this.props.config.defaultFilter || this.filterOptionsList[0],
- });
- }
- _onSelectFilter({ detail: key }) {
- this.state.selectedFilter = key;
- this.trigger('filter-selected', { filter: this.state.selectedFilter });
- }
- /**
- * When pressing vertical arrow keys, do not move the input cursor.
- */
- onSearchInputKeydown(event) {
- if (['ArrowUp', 'ArrowDown'].includes(event.key)) {
- event.preventDefault();
- }
- }
- /**
- * When vertical arrow keys are pressed, select fields for searching.
- * When enter key is pressed, trigger search event if there is searchInput.
- */
- onSearchInputKeyup(event) {
- if (['ArrowUp', 'ArrowDown'].includes(event.key)) {
- this.state.selectedSearchFieldId = this._fieldIdToSelect(event.key);
- } else if (event.key === 'Enter' || this.state.searchInput == '') {
- this._onClickSearchField({ detail: this.searchFieldsList[this.state.selectedSearchFieldId] });
- } else {
- if (this.state.selectedSearchFieldId === -1 && this.searchFieldsList.length) {
- this.state.selectedSearchFieldId = 0;
- }
- this.state.showSearchFields = true;
- }
- }
- /**
- * Called when a search field is clicked.
- */
- _onClickSearchField({ detail: fieldName }) {
- this.state.showSearchFields = false;
- this.trigger('search', { fieldName, searchTerm: this.state.searchInput });
- }
- /**
- * Given an arrow key, return the next selectedSearchFieldId.
- * E.g. If the selectedSearchFieldId is 1 and ArrowDown is pressed, return 2.
- *
- * @param {string} key vertical arrow key
- */
- _fieldIdToSelect(key) {
- const length = this.searchFieldsList.length;
- if (!length) return null;
- if (this.state.selectedSearchFieldId === -1) return 0;
- const current = this.state.selectedSearchFieldId || length;
- return (current + (key === 'ArrowDown' ? 1 : -1)) % length;
- }
- _hideOptions() {
- this.state.showFilterOptions = false;
- this.state.showSearchFields = false;
+ onSearchInputKeydown(event) {
+ if (["ArrowUp", "ArrowDown"].includes(event.key)) {
+ event.preventDefault();
}
}
- SearchBar.template = 'point_of_sale.SearchBar';
- Registries.Component.add(SearchBar);
+ /**
+ * When vertical arrow keys are pressed, select fields for searching.
+ * When enter key is pressed, trigger search event if there is searchInput.
+ */
+ onSearchInputKeyup(event) {
+ if (["ArrowUp", "ArrowDown"].includes(event.key)) {
+ this.state.selectedSearchFieldId = this._fieldIdToSelect(event.key);
+ } else if (event.key === "Enter" || this.state.searchInput == "") {
+ this._onClickSearchField({
+ detail: this.searchFieldsList[this.state.selectedSearchFieldId],
+ });
+ } else {
+ if (this.state.selectedSearchFieldId === -1 && this.searchFieldsList.length) {
+ this.state.selectedSearchFieldId = 0;
+ }
+ this.state.showSearchFields = true;
+ }
+ }
+ /**
+ * Called when a search field is clicked.
+ */
+ _onClickSearchField({ detail: fieldName }) {
+ this.state.showSearchFields = false;
+ this.trigger("search", { fieldName, searchTerm: this.state.searchInput });
+ }
+ /**
+ * Given an arrow key, return the next selectedSearchFieldId.
+ * E.g. If the selectedSearchFieldId is 1 and ArrowDown is pressed, return 2.
+ *
+ * @param {string} key vertical arrow key
+ */
+ _fieldIdToSelect(key) {
+ const length = this.searchFieldsList.length;
+ if (!length) {
+ return null;
+ }
+ if (this.state.selectedSearchFieldId === -1) {
+ return 0;
+ }
+ const current = this.state.selectedSearchFieldId || length;
+ return (current + (key === "ArrowDown" ? 1 : -1)) % length;
+ }
+ _hideOptions() {
+ this.state.showFilterOptions = false;
+ this.state.showSearchFields = false;
+ }
+}
+SearchBar.template = "point_of_sale.SearchBar";
+Registries.Component.add(SearchBar);
- return SearchBar;
-});
+export default SearchBar;
diff --git a/addons/point_of_sale/static/src/js/Notification.js b/addons/point_of_sale/static/src/js/Notification.js
index cf470676614..f0d92fca7ee 100644
--- a/addons/point_of_sale/static/src/js/Notification.js
+++ b/addons/point_of_sale/static/src/js/Notification.js
@@ -1,27 +1,25 @@
-odoo.define('point_of_sale.Notification', function (require) {
- 'use strict';
+/** @odoo-module */
- const { useListener } = require("@web/core/utils/hooks");
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { useListener } from "@web/core/utils/hooks";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { onMounted } = owl;
+const { onMounted } = owl;
- class Notification extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this.closeNotification);
+class Notification extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this.closeNotification);
- onMounted(() => {
- setTimeout(() => {
- this.closeNotification();
- }, this.props.duration)
- });
- }
+ onMounted(() => {
+ setTimeout(() => {
+ this.closeNotification();
+ }, this.props.duration);
+ });
}
- Notification.template = 'Notification';
+}
+Notification.template = "Notification";
- Registries.Component.add(Notification);
+Registries.Component.add(Notification);
- return Notification;
-});
+export default Notification;
diff --git a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js
index ce88df5fbc2..3041691a7d8 100644
--- a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js
@@ -1,64 +1,62 @@
-odoo.define('point_of_sale.AbstractAwaitablePopup', function (require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const { useBus } = require('@web/core/utils/hooks');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import { useBus } from "@web/core/utils/hooks";
- /**
- * Implement this abstract class by extending it like so:
- * ```js
- * class ConcretePopup extends AbstractAwaitablePopup {
- * async getPayload() {
- * return 'result';
- * }
- * }
- * ConcretePopup.template = xml`
- *
- *
- *
- *
- * `
- * ```
- *
- * The concrete popup can now be instantiated and be awaited for
- * the user's response like so:
- * ```js
- * const { confirmed, payload } = await this.showPopup('ConcretePopup');
- * // based on the implementation above,
- * // if confirmed, payload = 'result'
- * // otherwise, payload = null
- * ```
- */
- class AbstractAwaitablePopup extends PosComponent {
- setup() {
- super.setup();
- if (this.props.confirmKey) {
- useBus(this.env.posbus, `confirm-popup-${this.props.id}`, this.confirm);
- }
- if (this.props.cancelKey) {
- useBus(this.env.posbus, `cancel-popup-${this.props.id}`, this.cancel);
- }
+/**
+ * Implement this abstract class by extending it like so:
+ * ```js
+ * class ConcretePopup extends AbstractAwaitablePopup {
+ * async getPayload() {
+ * return 'result';
+ * }
+ * }
+ * ConcretePopup.template = xml`
+ *
+ *
+ *
+ *
+ * `
+ * ```
+ *
+ * The concrete popup can now be instantiated and be awaited for
+ * the user's response like so:
+ * ```js
+ * const { confirmed, payload } = await this.showPopup('ConcretePopup');
+ * // based on the implementation above,
+ * // if confirmed, payload = 'result'
+ * // otherwise, payload = null
+ * ```
+ */
+class AbstractAwaitablePopup extends PosComponent {
+ setup() {
+ super.setup();
+ if (this.props.confirmKey) {
+ useBus(this.env.posbus, `confirm-popup-${this.props.id}`, this.confirm);
}
- async confirm() {
- this.env.posbus.trigger('close-popup', {
- popupId: this.props.id,
- response: { confirmed: true, payload: await this.getPayload() },
- });
- }
- cancel() {
- this.env.posbus.trigger('close-popup', {
- popupId: this.props.id,
- response: { confirmed: false, payload: null },
- });
- }
- /**
- * Override this in the concrete popup implementation to set the
- * payload when the popup is confirmed.
- */
- async getPayload() {
- return null;
+ if (this.props.cancelKey) {
+ useBus(this.env.posbus, `cancel-popup-${this.props.id}`, this.cancel);
}
}
+ async confirm() {
+ this.env.posbus.trigger("close-popup", {
+ popupId: this.props.id,
+ response: { confirmed: true, payload: await this.getPayload() },
+ });
+ }
+ cancel() {
+ this.env.posbus.trigger("close-popup", {
+ popupId: this.props.id,
+ response: { confirmed: false, payload: null },
+ });
+ }
+ /**
+ * Override this in the concrete popup implementation to set the
+ * payload when the popup is confirmed.
+ */
+ async getPayload() {
+ return null;
+ }
+}
- return AbstractAwaitablePopup;
-});
+export default AbstractAwaitablePopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js b/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js
index 407c37625b6..a1cafb05b6a 100644
--- a/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js
@@ -1,57 +1,55 @@
-odoo.define('point_of_sale.CashMovePopup', function (require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { parse } = require('web.field_utils');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { parse } from "web.field_utils";
- const { useRef, useState, onMounted } = owl;
+const { useRef, useState, onMounted } = owl;
- class CashMovePopup extends AbstractAwaitablePopup {
- setup() {
- super.setup();
- this.state = useState({
- inputType: 'out', // '' | 'in' | 'out'
- inputAmount: '',
- inputReason: '',
- errorMessage: '',
- });
- this.inputAmountRef = useRef('input-amount-ref');
- onMounted(() => this.inputAmountRef.el.focus());
+class CashMovePopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ this.state = useState({
+ inputType: "out", // '' | 'in' | 'out'
+ inputAmount: "",
+ inputReason: "",
+ errorMessage: "",
+ });
+ this.inputAmountRef = useRef("input-amount-ref");
+ onMounted(() => this.inputAmountRef.el.focus());
+ }
+ confirm() {
+ try {
+ parse.float(this.state.inputAmount);
+ } catch {
+ this.state.errorMessage = this.env._t("Invalid amount");
+ return;
}
- confirm() {
- try {
- parse.float(this.state.inputAmount);
- } catch {
- this.state.errorMessage = this.env._t('Invalid amount');
- return;
- }
- if (this.state.inputAmount < 0) {
- this.state.errorMessage = this.env._t('Insert a positive amount');
- return;
- }
- return super.confirm();
+ if (this.state.inputAmount < 0) {
+ this.state.errorMessage = this.env._t("Insert a positive amount");
+ return;
}
- _onAmountKeypress(event) {
- if (['-', '+'].includes(event.key)) {
- event.preventDefault();
- }
- }
- onClickButton(type) {
- this.state.inputType = type;
- this.state.errorMessage = '';
- this.inputAmountRef.el.focus();
- }
- getPayload() {
- return {
- amount: parse.float(this.state.inputAmount),
- reason: this.state.inputReason.trim(),
- type: this.state.inputType,
- };
+ return super.confirm();
+ }
+ _onAmountKeypress(event) {
+ if (["-", "+"].includes(event.key)) {
+ event.preventDefault();
}
}
- CashMovePopup.template = 'point_of_sale.CashMovePopup';
- Registries.Component.add(CashMovePopup);
+ onClickButton(type) {
+ this.state.inputType = type;
+ this.state.errorMessage = "";
+ this.inputAmountRef.el.focus();
+ }
+ getPayload() {
+ return {
+ amount: parse.float(this.state.inputAmount),
+ reason: this.state.inputReason.trim(),
+ type: this.state.inputType,
+ };
+ }
+}
+CashMovePopup.template = "point_of_sale.CashMovePopup";
+Registries.Component.add(CashMovePopup);
- return CashMovePopup;
-});
+export default CashMovePopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js
index f234267b34e..59c77266ba9 100644
--- a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js
@@ -1,58 +1,58 @@
-odoo.define('point_of_sale.CashOpeningPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
- const { useState } = owl;
+const { useState } = owl;
- class CashOpeningPopup extends AbstractAwaitablePopup {
- setup() {
- super.setup();
- this.manualInputCashCount = null;
- this.moneyDetails = null;
- this.state = useState({
- notes: "",
- openingCash: this.env.pos.pos_session.cash_register_balance_start || 0,
- });
- }
- //@override
- async confirm() {
- this.env.pos.pos_session.cash_register_balance_start = this.state.openingCash;
- this.env.pos.pos_session.state = 'opened';
- this.rpc({
- model: 'pos.session',
- method: 'set_cashbox_pos',
- args: [this.env.pos.pos_session.id, this.state.openingCash, this.state.notes],
- });
- super.confirm();
- }
- async openDetailsPopup() {
- const { confirmed, payload } = await this.showPopup('MoneyDetailsPopup', {
- moneyDetails: this.moneyDetails, total: this.manualInputCashCount ? 0 : this.state.openingCash });
- if (confirmed) {
- const { total, moneyDetails, moneyDetailsNotes } = payload;
- this.state.openingCash = total;
- if (moneyDetailsNotes) {
- this.state.notes = moneyDetailsNotes;
- }
- this.manualInputCashCount = false;
- this.moneyDetails = moneyDetails;
- }
- }
- handleInputChange() {
- this.manualInputCashCount = true;
- this.moneyDetails = null;
- this.state.notes = "";
- if (typeof(this.state.openingCash) !== "number") {
- this.state.openingCash = 0;
+class CashOpeningPopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ this.manualInputCashCount = null;
+ this.moneyDetails = null;
+ this.state = useState({
+ notes: "",
+ openingCash: this.env.pos.pos_session.cash_register_balance_start || 0,
+ });
+ }
+ //@override
+ async confirm() {
+ this.env.pos.pos_session.cash_register_balance_start = this.state.openingCash;
+ this.env.pos.pos_session.state = "opened";
+ this.rpc({
+ model: "pos.session",
+ method: "set_cashbox_pos",
+ args: [this.env.pos.pos_session.id, this.state.openingCash, this.state.notes],
+ });
+ super.confirm();
+ }
+ async openDetailsPopup() {
+ const { confirmed, payload } = await this.showPopup("MoneyDetailsPopup", {
+ moneyDetails: this.moneyDetails,
+ total: this.manualInputCashCount ? 0 : this.state.openingCash,
+ });
+ if (confirmed) {
+ const { total, moneyDetails, moneyDetailsNotes } = payload;
+ this.state.openingCash = total;
+ if (moneyDetailsNotes) {
+ this.state.notes = moneyDetailsNotes;
}
+ this.manualInputCashCount = false;
+ this.moneyDetails = moneyDetails;
}
}
+ handleInputChange() {
+ this.manualInputCashCount = true;
+ this.moneyDetails = null;
+ this.state.notes = "";
+ if (typeof this.state.openingCash !== "number") {
+ this.state.openingCash = 0;
+ }
+ }
+}
- CashOpeningPopup.template = 'CashOpeningPopup';
- CashOpeningPopup.defaultProps = { cancelKey: false };
- Registries.Component.add(CashOpeningPopup);
+CashOpeningPopup.template = "CashOpeningPopup";
+CashOpeningPopup.defaultProps = { cancelKey: false };
+Registries.Component.add(CashOpeningPopup);
- return CashOpeningPopup;
-});
+export default CashOpeningPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
index 4efdf008f5a..f0e9e3fe780 100644
--- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
@@ -1,174 +1,192 @@
-odoo.define('point_of_sale.ClosePosPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { identifyError } = require('point_of_sale.utils');
- const { ConnectionLostError, ConnectionAbortedError} = require('@web/core/network/rpc_service')
- const { useState } = owl;
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { identifyError } from "@point_of_sale/js/utils";
+import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service";
+const { useState } = owl;
- class ClosePosPopup extends AbstractAwaitablePopup {
- setup() {
- super.setup();
- this.manualInputCashCount = false;
- this.cashControl = this.env.pos.config.cash_control;
- this.closeSessionClicked = false;
- this.moneyDetails = null;
- Object.assign(this, this.props.info);
- this.state = useState({
- displayMoneyDetailsPopup: false,
- });
- Object.assign(this.state, this.props.info.state);
- }
- //@override
- async confirm() {
- if (!this.cashControl || !this.hasDifference()) {
- this.closeSession();
- } else if (this.hasUserAuthority()) {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Payments Difference'),
- body: this.env._t('Do you want to accept payments difference and post a profit/loss journal entry?'),
- });
- if (confirmed) {
- this.closeSession();
- }
- } else {
- await this.showPopup('ConfirmPopup', {
- title: this.env._t('Payments Difference'),
- body: _.str.sprintf(
- this.env._t('The maximum difference allowed is %s.\n\
- Please contact your manager to accept the closing difference.'),
- this.env.pos.format_currency(this.amountAuthorizedDiff)
- ),
- confirmText: this.env._t('OK'),
- })
- }
- }
- //@override
- async cancel() {
- if (this.canCancel()) {
- super.cancel();
- }
- }
- async openDetailsPopup() {
- const { confirmed, payload } = await this.showPopup('MoneyDetailsPopup', {
- moneyDetails: this.moneyDetails,
- total: this.manualInputCashCount ? 0 : this.state.payments[this.defaultCashDetails.id].counted,
+class ClosePosPopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ this.manualInputCashCount = false;
+ this.cashControl = this.env.pos.config.cash_control;
+ this.closeSessionClicked = false;
+ this.moneyDetails = null;
+ Object.assign(this, this.props.info);
+ this.state = useState({
+ displayMoneyDetailsPopup: false,
+ });
+ Object.assign(this.state, this.props.info.state);
+ }
+ //@override
+ async confirm() {
+ if (!this.cashControl || !this.hasDifference()) {
+ this.closeSession();
+ } else if (this.hasUserAuthority()) {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Payments Difference"),
+ body: this.env._t(
+ "Do you want to accept payments difference and post a profit/loss journal entry?"
+ ),
});
if (confirmed) {
- const { total, moneyDetailsNotes, moneyDetails } = payload;
- 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);
- if (moneyDetailsNotes) {
- this.state.notes = moneyDetailsNotes;
- }
- this.manualInputCashCount = false;
- this.moneyDetails = moneyDetails;
+ this.closeSession();
}
- }
- async downloadSalesReport() {
- await this.env.legacyActionManager.do_action('point_of_sale.sale_details_report', {
- additional_context: {
- active_ids: [this.env.pos.pos_session.id],
- },
+ } else {
+ await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Payments Difference"),
+ body: _.str.sprintf(
+ this.env._t(
+ "The maximum difference allowed is %s.\n\
+ Please contact your manager to accept the closing difference."
+ ),
+ this.env.pos.format_currency(this.amountAuthorizedDiff)
+ ),
+ confirmText: this.env._t("OK"),
});
}
- handleInputChange(paymentId) {
- let expectedAmount;
- if (paymentId === this.defaultCashDetails.id) {
- this.manualInputCashCount = true;
- this.moneyDetails = null;
- this.state.notes = '';
- expectedAmount = this.defaultCashDetails.amount;
- } else {
- expectedAmount = this.otherPaymentMethods.find(pm => paymentId === pm.id).amount;
- }
- this.state.payments[paymentId].difference =
- this.env.pos.round_decimals_currency(this.state.payments[paymentId].counted - expectedAmount);
- }
- hasDifference() {
- return Object.entries(this.state.payments).find(pm => pm[1].difference != 0);
- }
- hasUserAuthority() {
- const absDifferences = Object.entries(this.state.payments).map(pm => Math.abs(pm[1].difference));
- return this.isManager || this.amountAuthorizedDiff == null || Math.max(...absDifferences) <= this.amountAuthorizedDiff;
- }
- canCancel() {
- return true;
- }
- closePos() {
- this.trigger('close-pos');
- }
- async closeSession() {
- if (!this.closeSessionClicked) {
- this.closeSessionClicked = true;
- let response;
- if (this.cashControl) {
- response = await this.rpc({
- model: 'pos.session',
- method: 'post_closing_cash_details',
- args: [this.env.pos.pos_session.id],
- kwargs: {
- counted_cash: this.state.payments[this.defaultCashDetails.id].counted,
- }
- })
- if (!response.successful) {
- return this.handleClosingError(response);
- }
- }
- await this.rpc({
- model: 'pos.session',
- method: 'update_closing_control_state_session',
- args: [this.env.pos.pos_session.id, this.state.notes]
- })
- try {
- const bankPaymentMethodDiffPairs = this.otherPaymentMethods
- .filter((pm) => pm.type == 'bank')
- .map((pm) => [pm.id, this.state.payments[pm.id].difference]);
- response = await this.rpc({
- model: 'pos.session',
- method: 'close_session_from_ui',
- args: [this.env.pos.pos_session.id, bankPaymentMethodDiffPairs],
- context: this.env.session.user_context,
- });
- if (!response.successful) {
- return this.handleClosingError(response);
- }
- window.location = '/web#action=point_of_sale.action_client_pos_menu';
- } catch (error) {
- const iError = identifyError(error);
- if (iError instanceof ConnectionLostError || iError instanceof ConnectionAbortedError) {
- await this.showPopup('ErrorPopup', {
- title: this.env._t('Network Error'),
- body: this.env._t('Cannot close the session when offline.'),
- });
- } else {
- await this.showPopup('ErrorPopup', {
- title: this.env._t('Closing session error'),
- body: this.env._t(
- 'An error has occurred when trying to close the session.\n' +
- 'You will be redirected to the back-end to manually close the session.')
- })
- window.location = '/web#action=point_of_sale.action_client_pos_menu';
- }
- }
- this.closeSessionClicked = false;
- }
- }
- async handleClosingError(response) {
- await this.showPopup('ErrorPopup', {title: 'Error', body: response.message});
- if (response.redirect) {
- window.location = '/web#action=point_of_sale.action_client_pos_menu';
- }
- }
- _getShowDiff(pm) {
- return pm.type == 'bank' && pm.number !== 0;
+ }
+ //@override
+ async cancel() {
+ if (this.canCancel()) {
+ super.cancel();
}
}
+ async openDetailsPopup() {
+ const { confirmed, payload } = await this.showPopup("MoneyDetailsPopup", {
+ moneyDetails: this.moneyDetails,
+ total: this.manualInputCashCount
+ ? 0
+ : this.state.payments[this.defaultCashDetails.id].counted,
+ });
+ if (confirmed) {
+ const { total, moneyDetailsNotes, moneyDetails } = payload;
+ 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
+ );
+ if (moneyDetailsNotes) {
+ this.state.notes = moneyDetailsNotes;
+ }
+ this.manualInputCashCount = false;
+ this.moneyDetails = moneyDetails;
+ }
+ }
+ async downloadSalesReport() {
+ await this.env.legacyActionManager.do_action("point_of_sale.sale_details_report", {
+ additional_context: {
+ active_ids: [this.env.pos.pos_session.id],
+ },
+ });
+ }
+ handleInputChange(paymentId) {
+ let expectedAmount;
+ if (paymentId === this.defaultCashDetails.id) {
+ this.manualInputCashCount = true;
+ this.moneyDetails = null;
+ this.state.notes = "";
+ expectedAmount = this.defaultCashDetails.amount;
+ } else {
+ expectedAmount = this.otherPaymentMethods.find((pm) => paymentId === pm.id).amount;
+ }
+ this.state.payments[paymentId].difference = this.env.pos.round_decimals_currency(
+ this.state.payments[paymentId].counted - expectedAmount
+ );
+ }
+ hasDifference() {
+ return Object.entries(this.state.payments).find((pm) => pm[1].difference != 0);
+ }
+ hasUserAuthority() {
+ const absDifferences = Object.entries(this.state.payments).map((pm) =>
+ Math.abs(pm[1].difference)
+ );
+ return (
+ this.isManager ||
+ this.amountAuthorizedDiff == null ||
+ Math.max(...absDifferences) <= this.amountAuthorizedDiff
+ );
+ }
+ canCancel() {
+ return true;
+ }
+ closePos() {
+ this.trigger("close-pos");
+ }
+ async closeSession() {
+ if (!this.closeSessionClicked) {
+ this.closeSessionClicked = true;
+ let response;
+ if (this.cashControl) {
+ response = await this.rpc({
+ model: "pos.session",
+ method: "post_closing_cash_details",
+ args: [this.env.pos.pos_session.id],
+ kwargs: {
+ counted_cash: this.state.payments[this.defaultCashDetails.id].counted,
+ },
+ });
+ if (!response.successful) {
+ return this.handleClosingError(response);
+ }
+ }
+ await this.rpc({
+ model: "pos.session",
+ method: "update_closing_control_state_session",
+ args: [this.env.pos.pos_session.id, this.state.notes],
+ });
+ try {
+ const bankPaymentMethodDiffPairs = this.otherPaymentMethods
+ .filter((pm) => pm.type == "bank")
+ .map((pm) => [pm.id, this.state.payments[pm.id].difference]);
+ response = await this.rpc({
+ model: "pos.session",
+ method: "close_session_from_ui",
+ args: [this.env.pos.pos_session.id, bankPaymentMethodDiffPairs],
+ context: this.env.session.user_context,
+ });
+ if (!response.successful) {
+ return this.handleClosingError(response);
+ }
+ window.location = "/web#action=point_of_sale.action_client_pos_menu";
+ } catch (error) {
+ const iError = identifyError(error);
+ if (
+ iError instanceof ConnectionLostError ||
+ iError instanceof ConnectionAbortedError
+ ) {
+ await this.showPopup("ErrorPopup", {
+ title: this.env._t("Network Error"),
+ body: this.env._t("Cannot close the session when offline."),
+ });
+ } else {
+ await this.showPopup("ErrorPopup", {
+ title: this.env._t("Closing session error"),
+ body: this.env._t(
+ "An error has occurred when trying to close the session.\n" +
+ "You will be redirected to the back-end to manually close the session."
+ ),
+ });
+ window.location = "/web#action=point_of_sale.action_client_pos_menu";
+ }
+ }
+ this.closeSessionClicked = false;
+ }
+ }
+ async handleClosingError(response) {
+ await this.showPopup("ErrorPopup", { title: "Error", body: response.message });
+ if (response.redirect) {
+ window.location = "/web#action=point_of_sale.action_client_pos_menu";
+ }
+ }
+ _getShowDiff(pm) {
+ return pm.type == "bank" && pm.number !== 0;
+ }
+}
- ClosePosPopup.template = 'ClosePosPopup';
- Registries.Component.add(ClosePosPopup);
+ClosePosPopup.template = "ClosePosPopup";
+Registries.Component.add(ClosePosPopup);
- return ClosePosPopup;
-});
+export default ClosePosPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/ConfirmPopup.js b/addons/point_of_sale/static/src/js/Popups/ConfirmPopup.js
index 4536feb3e94..106ba159911 100644
--- a/addons/point_of_sale/static/src/js/Popups/ConfirmPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ConfirmPopup.js
@@ -1,21 +1,19 @@
-odoo.define('point_of_sale.ConfirmPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- // formerly ConfirmPopupWidget
- class ConfirmPopup extends AbstractAwaitablePopup {}
- ConfirmPopup.template = 'ConfirmPopup';
- ConfirmPopup.defaultProps = {
- confirmText: _lt('Ok'),
- cancelText: _lt('Cancel'),
- title: _lt('Confirm ?'),
- body: '',
- };
+// formerly ConfirmPopupWidget
+class ConfirmPopup extends AbstractAwaitablePopup {}
+ConfirmPopup.template = "ConfirmPopup";
+ConfirmPopup.defaultProps = {
+ confirmText: _lt("Ok"),
+ cancelText: _lt("Cancel"),
+ title: _lt("Confirm ?"),
+ body: "",
+};
- Registries.Component.add(ConfirmPopup);
+Registries.Component.add(ConfirmPopup);
- return ConfirmPopup;
-});
+export default ConfirmPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/ControlButtonPopup.js b/addons/point_of_sale/static/src/js/Popups/ControlButtonPopup.js
index 924accf4b7f..2d4eb65b541 100644
--- a/addons/point_of_sale/static/src/js/Popups/ControlButtonPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ControlButtonPopup.js
@@ -1,28 +1,26 @@
-odoo.define('point_of_sale.ControlButtonPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- class ControlButtonPopup extends AbstractAwaitablePopup {
- /**
- * @param {Object} props
- * @param {string} props.startingValue
- */
- setup() {
- super.setup();
- this.controlButtons = this.props.controlButtons;
- }
+class ControlButtonPopup extends AbstractAwaitablePopup {
+ /**
+ * @param {Object} props
+ * @param {string} props.startingValue
+ */
+ setup() {
+ super.setup();
+ this.controlButtons = this.props.controlButtons;
}
- ControlButtonPopup.template = 'ControlButtonPopup';
- ControlButtonPopup.defaultProps = {
- cancelText: _lt('Back'),
- controlButtons: [],
- confirmKey: false,
- };
+}
+ControlButtonPopup.template = "ControlButtonPopup";
+ControlButtonPopup.defaultProps = {
+ cancelText: _lt("Back"),
+ controlButtons: [],
+ confirmKey: false,
+};
- Registries.Component.add(ControlButtonPopup);
+Registries.Component.add(ControlButtonPopup);
- return ControlButtonPopup;
-});
+export default ControlButtonPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/EditListInput.js b/addons/point_of_sale/static/src/js/Popups/EditListInput.js
index e4e7769685f..3b48b008fae 100644
--- a/addons/point_of_sale/static/src/js/Popups/EditListInput.js
+++ b/addons/point_of_sale/static/src/js/Popups/EditListInput.js
@@ -1,29 +1,27 @@
-odoo.define('point_of_sale.EditListInput', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- /**
- * props {
- * createNewItem: callback,
- * removeItem: callback,
- * item: object,
- * }
- */
- class EditListInput extends PosComponent {
- onKeyup(event) {
- if (event.key === "Enter" && event.target.value.trim() !== '') {
- this.props.createNewItem();
- }
- }
- onInput(event) {
- this.props.onInputChange(this.props.item._id, event.target.value);
+/**
+ * props {
+ * createNewItem: callback,
+ * removeItem: callback,
+ * item: object,
+ * }
+ */
+class EditListInput extends PosComponent {
+ onKeyup(event) {
+ if (event.key === "Enter" && event.target.value.trim() !== "") {
+ this.props.createNewItem();
}
}
- EditListInput.template = 'EditListInput';
+ onInput(event) {
+ this.props.onInputChange(this.props.item._id, event.target.value);
+ }
+}
+EditListInput.template = "EditListInput";
- Registries.Component.add(EditListInput);
+Registries.Component.add(EditListInput);
- return EditListInput;
-});
+export default EditListInput;
diff --git a/addons/point_of_sale/static/src/js/Popups/EditListPopup.js b/addons/point_of_sale/static/src/js/Popups/EditListPopup.js
index e5fc46198fa..c1aa8220674 100644
--- a/addons/point_of_sale/static/src/js/Popups/EditListPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/EditListPopup.js
@@ -1,109 +1,117 @@
-odoo.define('point_of_sale.EditListPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { useAutoFocusToLast } = require('point_of_sale.custom_hooks');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { useAutoFocusToLast } from "@point_of_sale/js/custom_hooks";
+import { _lt } from "@web/core/l10n/translation";
- const { useState } = owl;
+const { useState } = owl;
+/**
+ * Given a array of { id, text }, we show the user this popup to be able to modify this given array.
+ * (used to replace PackLotLinePopupWidget)
+ *
+ * The expected return of showPopup when this popup is used is an array of { _id, [id], text }.
+ * - _id is the assigned unique identifier for each item.
+ * - id is the original id. if not provided, then it means that the item is new.
+ * - text is the modified/unmodified text.
+ *
+ * Example:
+ *
+ * ```
+ * -- perhaps inside a click handler --
+ * // gather the items to edit
+ * const names = [{ id: 1, text: 'Joseph'}, { id: 2, text: 'Kaykay' }];
+ *
+ * // supply the items to the popup and wait for user's response
+ * // when user pressed `confirm` in the popup, the changes he made will be returned by the showPopup function.
+ * const { confirmed, payload: newNames } = await this.showPopup('EditListPopup', {
+ * title: "Can you confirm this item?",
+ * array: names })
+ *
+ * // we then consume the new data. In this example, it is only logged.
+ * if (confirmed) {
+ * console.log(newNames);
+ * // the above might log the following:
+ * // [{ _id: 1, id: 1, text: 'Joseph Caburnay' }, { _id: 2, id: 2, 'Kaykay' }, { _id: 3, 'James' }]
+ * // The result showed that the original item with id=1 was changed to have text 'Joseph Caburnay' from 'Joseph'
+ * // The one with id=2 did not change. And a new item with text='James' is added.
+ * }
+ * ```
+ */
+class EditListPopup extends AbstractAwaitablePopup {
/**
- * Given a array of { id, text }, we show the user this popup to be able to modify this given array.
- * (used to replace PackLotLinePopupWidget)
- *
- * The expected return of showPopup when this popup is used is an array of { _id, [id], text }.
- * - _id is the assigned unique identifier for each item.
- * - id is the original id. if not provided, then it means that the item is new.
- * - text is the modified/unmodified text.
- *
- * Example:
- *
- * ```
- * -- perhaps inside a click handler --
- * // gather the items to edit
- * const names = [{ id: 1, text: 'Joseph'}, { id: 2, text: 'Kaykay' }];
- *
- * // supply the items to the popup and wait for user's response
- * // when user pressed `confirm` in the popup, the changes he made will be returned by the showPopup function.
- * const { confirmed, payload: newNames } = await this.showPopup('EditListPopup', {
- * title: "Can you confirm this item?",
- * array: names })
- *
- * // we then consume the new data. In this example, it is only logged.
- * if (confirmed) {
- * console.log(newNames);
- * // the above might log the following:
- * // [{ _id: 1, id: 1, text: 'Joseph Caburnay' }, { _id: 2, id: 2, 'Kaykay' }, { _id: 3, 'James' }]
- * // The result showed that the original item with id=1 was changed to have text 'Joseph Caburnay' from 'Joseph'
- * // The one with id=2 did not change. And a new item with text='James' is added.
- * }
- * ```
+ * @param {String} title required title of popup
+ * @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)
*/
- class EditListPopup extends AbstractAwaitablePopup {
- /**
- * @param {String} title required title of popup
- * @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)
- */
- setup() {
- super.setup();
- this._id = 0;
- this.state = useState({ array: this._initialize(this.props.array) });
- useAutoFocusToLast();
- }
- _nextId() {
- return this._id++;
- }
- _emptyItem() {
- return {
- text: '',
- _id: this._nextId(),
- };
- }
- _initialize(array) {
- // If no array is provided, we initialize with one empty item.
- if (array.length === 0) return [this._emptyItem()];
- // Put _id for each item. It will serve as unique identifier of each item.
- return array.map((item) => Object.assign({}, { _id: this._nextId() }, typeof item === 'object'? item: { 'text': item}));
- }
- _hasMoreThanOneItem() {
- return this.state.array.length > 1;
- }
- removeItem(itemId) {
- this.state.array.splice(
- this.state.array.findIndex(item => item._id == itemId),
- 1
- );
- }
- onInputChange(itemId, text) {
- const item = this.state.array.find(elem => elem._id === itemId);
- item.text = text;
- }
- createNewItem() {
- if (this.props.isSingleItem) return;
- this.state.array.push(this._emptyItem());
- }
- /**
- * @override
- */
- getPayload() {
- return {
- newArray: this.state.array
- .filter((item) => item.text.trim() !== '')
- .map((item) => Object.assign({}, item)),
- };
- }
+ setup() {
+ super.setup();
+ this._id = 0;
+ this.state = useState({ array: this._initialize(this.props.array) });
+ useAutoFocusToLast();
}
- EditListPopup.template = 'EditListPopup';
- EditListPopup.defaultProps = {
- confirmText: _lt('Add'),
- cancelText: _lt('Discard'),
- array: [],
- isSingleItem: false,
- };
+ _nextId() {
+ return this._id++;
+ }
+ _emptyItem() {
+ return {
+ text: "",
+ _id: this._nextId(),
+ };
+ }
+ _initialize(array) {
+ // If no array is provided, we initialize with one empty item.
+ if (array.length === 0) {
+ return [this._emptyItem()];
+ }
+ // Put _id for each item. It will serve as unique identifier of each item.
+ return array.map((item) =>
+ Object.assign(
+ {},
+ { _id: this._nextId() },
+ typeof item === "object" ? item : { text: item }
+ )
+ );
+ }
+ _hasMoreThanOneItem() {
+ return this.state.array.length > 1;
+ }
+ removeItem(itemId) {
+ this.state.array.splice(
+ this.state.array.findIndex((item) => item._id == itemId),
+ 1
+ );
+ }
+ onInputChange(itemId, text) {
+ const item = this.state.array.find((elem) => elem._id === itemId);
+ item.text = text;
+ }
+ createNewItem() {
+ if (this.props.isSingleItem) {
+ return;
+ }
+ this.state.array.push(this._emptyItem());
+ }
+ /**
+ * @override
+ */
+ getPayload() {
+ return {
+ newArray: this.state.array
+ .filter((item) => item.text.trim() !== "")
+ .map((item) => Object.assign({}, item)),
+ };
+ }
+}
+EditListPopup.template = "EditListPopup";
+EditListPopup.defaultProps = {
+ confirmText: _lt("Add"),
+ cancelText: _lt("Discard"),
+ array: [],
+ isSingleItem: false,
+};
- Registries.Component.add(EditListPopup);
+Registries.Component.add(EditListPopup);
- return EditListPopup;
-});
+export default EditListPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/ErrorBarcodePopup.js b/addons/point_of_sale/static/src/js/Popups/ErrorBarcodePopup.js
index ded7dbdca80..9a43be839cc 100644
--- a/addons/point_of_sale/static/src/js/Popups/ErrorBarcodePopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ErrorBarcodePopup.js
@@ -1,27 +1,26 @@
-odoo.define('point_of_sale.ErrorBarcodePopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const ErrorPopup = require('point_of_sale.ErrorPopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import ErrorPopup from "@point_of_sale/js/Popups/ErrorPopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- // formerly ErrorBarcodePopupWidget
- class ErrorBarcodePopup extends ErrorPopup {
- get translatedMessage() {
- return this.env._t(this.props.message);
- }
+// formerly ErrorBarcodePopupWidget
+class ErrorBarcodePopup extends ErrorPopup {
+ get translatedMessage() {
+ return this.env._t(this.props.message);
}
- ErrorBarcodePopup.template = 'ErrorBarcodePopup';
- ErrorBarcodePopup.defaultProps = {
- confirmText: _lt('Ok'),
- cancelText: _lt('Cancel'),
- title: _lt('Error'),
- body: '',
- message:
- _lt('The Point of Sale could not find any product, customer, employee or action associated with the scanned barcode.'),
- };
+}
+ErrorBarcodePopup.template = "ErrorBarcodePopup";
+ErrorBarcodePopup.defaultProps = {
+ confirmText: _lt("Ok"),
+ cancelText: _lt("Cancel"),
+ title: _lt("Error"),
+ body: "",
+ message: _lt(
+ "The Point of Sale could not find any product, customer, employee or action associated with the scanned barcode."
+ ),
+};
- Registries.Component.add(ErrorBarcodePopup);
+Registries.Component.add(ErrorBarcodePopup);
- return ErrorBarcodePopup;
-});
+export default ErrorBarcodePopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js b/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js
index 8300b6a5749..d685efa91c8 100644
--- a/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ErrorPopup.js
@@ -1,29 +1,27 @@
-odoo.define('point_of_sale.ErrorPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- // formerly ErrorPopupWidget
- class ErrorPopup extends AbstractAwaitablePopup {
- setup() {
- super.setup();
- owl.onMounted(this.onMounted);
- }
- onMounted() {
- this.playSound('error');
- }
+// formerly ErrorPopupWidget
+class ErrorPopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ owl.onMounted(this.onMounted);
}
- ErrorPopup.template = 'ErrorPopup';
- ErrorPopup.defaultProps = {
- confirmText: _lt('Ok'),
- title: _lt('Error'),
- body: '',
- cancelKey: false,
- };
+ onMounted() {
+ this.playSound("error");
+ }
+}
+ErrorPopup.template = "ErrorPopup";
+ErrorPopup.defaultProps = {
+ confirmText: _lt("Ok"),
+ title: _lt("Error"),
+ body: "",
+ cancelKey: false,
+};
- Registries.Component.add(ErrorPopup);
+Registries.Component.add(ErrorPopup);
- return ErrorPopup;
-});
+export default ErrorPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js b/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js
index c8990ad5578..42dbb93a590 100644
--- a/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js
@@ -1,46 +1,44 @@
-odoo.define('point_of_sale.ErrorTracebackPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const ErrorPopup = require('point_of_sale.ErrorPopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import ErrorPopup from "@point_of_sale/js/Popups/ErrorPopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- // formerly ErrorTracebackPopupWidget
- class ErrorTracebackPopup extends ErrorPopup {
- get tracebackUrl() {
- const blob = new Blob([this.props.body]);
- const URL = window.URL || window.webkitURL;
- return URL.createObjectURL(blob);
- }
- get tracebackFilename() {
- return `${this.env._t('error')} ${moment().format('YYYY-MM-DD-HH-mm-ss')}.txt`;
- }
- emailTraceback() {
- const address = this.env.pos.company.email;
- const subject = this.env._t('IMPORTANT: Bug Report From Odoo Point Of Sale');
- window.open(
- 'mailto:' +
- address +
- '?subject=' +
- (subject ? window.encodeURIComponent(subject) : '') +
- '&body=' +
- (this.props.body ? window.encodeURIComponent(this.props.body) : '')
- );
- }
+// formerly ErrorTracebackPopupWidget
+class ErrorTracebackPopup extends ErrorPopup {
+ get tracebackUrl() {
+ const blob = new Blob([this.props.body]);
+ const URL = window.URL || window.webkitURL;
+ return URL.createObjectURL(blob);
}
- ErrorTracebackPopup.template = 'ErrorTracebackPopup';
- ErrorTracebackPopup.defaultProps = {
- confirmText: _lt('Ok'),
- cancelText: _lt('Cancel'),
- confirmKey: false,
- title: _lt('Error with Traceback'),
- body: '',
- exitButtonIsShown: false,
- exitButtonText: _lt('Exit Pos'),
- exitButtonTrigger: 'close-pos'
- };
+ get tracebackFilename() {
+ return `${this.env._t("error")} ${moment().format("YYYY-MM-DD-HH-mm-ss")}.txt`;
+ }
+ emailTraceback() {
+ const address = this.env.pos.company.email;
+ const subject = this.env._t("IMPORTANT: Bug Report From Odoo Point Of Sale");
+ window.open(
+ "mailto:" +
+ address +
+ "?subject=" +
+ (subject ? window.encodeURIComponent(subject) : "") +
+ "&body=" +
+ (this.props.body ? window.encodeURIComponent(this.props.body) : "")
+ );
+ }
+}
+ErrorTracebackPopup.template = "ErrorTracebackPopup";
+ErrorTracebackPopup.defaultProps = {
+ confirmText: _lt("Ok"),
+ cancelText: _lt("Cancel"),
+ confirmKey: false,
+ title: _lt("Error with Traceback"),
+ body: "",
+ exitButtonIsShown: false,
+ exitButtonText: _lt("Exit Pos"),
+ exitButtonTrigger: "close-pos",
+};
- Registries.Component.add(ErrorTracebackPopup);
+Registries.Component.add(ErrorTracebackPopup);
- return ErrorTracebackPopup;
-});
+export default ErrorTracebackPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js b/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js
index 9a7750d3ca0..5068a23317a 100644
--- a/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js
@@ -1,48 +1,58 @@
-odoo.define('point_of_sale.MoneyDetailsPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
- const { useState } = owl;
+const { useState } = owl;
- class MoneyDetailsPopup extends AbstractAwaitablePopup{
- setup() {
- super.setup();
- this.currency = this.env.pos.currency;
- this.state = useState({
- moneyDetails: this.props.moneyDetails ? {...this.props.moneyDetails} :
- Object.fromEntries(this.env.pos.bills.map(bill => ([bill.value, 0]))),
- total: this.props.total ? this.props.total : 0,
- });
- }
- get firstHalfMoneyDetails() {
- const moneyDetailsKeys = Object.keys(this.state.moneyDetails).sort((a, b) => a - b);
- return moneyDetailsKeys.slice(0, Math.ceil(moneyDetailsKeys.length/2));
- }
- get lastHalfMoneyDetails() {
- const moneyDetailsKeys = Object.keys(this.state.moneyDetails).sort((a, b) => a - b);
- return moneyDetailsKeys.slice(Math.ceil(moneyDetailsKeys.length/2), moneyDetailsKeys.length);
- }
- 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);
- }
- //@override
- async getPayload() {
- let moneyDetailsNotes = this.state.total ? 'Money details: \n' : null;
- this.env.pos.bills.forEach(bill => {
- if (this.state.moneyDetails[bill.value]) {
- moneyDetailsNotes += ` - ${this.state.moneyDetails[bill.value]} x ${this.env.pos.format_currency(bill.value)}\n`;
- }
- })
- return { total: this.state.total, moneyDetailsNotes, moneyDetails: { ...this.state.moneyDetails } };
- }
+class MoneyDetailsPopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ this.currency = this.env.pos.currency;
+ this.state = useState({
+ moneyDetails: this.props.moneyDetails
+ ? { ...this.props.moneyDetails }
+ : Object.fromEntries(this.env.pos.bills.map((bill) => [bill.value, 0])),
+ total: this.props.total ? this.props.total : 0,
+ });
}
+ get firstHalfMoneyDetails() {
+ const moneyDetailsKeys = Object.keys(this.state.moneyDetails).sort((a, b) => a - b);
+ return moneyDetailsKeys.slice(0, Math.ceil(moneyDetailsKeys.length / 2));
+ }
+ get lastHalfMoneyDetails() {
+ const moneyDetailsKeys = Object.keys(this.state.moneyDetails).sort((a, b) => a - b);
+ return moneyDetailsKeys.slice(
+ Math.ceil(moneyDetailsKeys.length / 2),
+ moneyDetailsKeys.length
+ );
+ }
+ updateMoneyDetailsAmount() {
+ const 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);
+ }
+ //@override
+ async getPayload() {
+ let moneyDetailsNotes = this.state.total ? "Money details: \n" : null;
+ this.env.pos.bills.forEach((bill) => {
+ if (this.state.moneyDetails[bill.value]) {
+ moneyDetailsNotes += ` - ${
+ this.state.moneyDetails[bill.value]
+ } x ${this.env.pos.format_currency(bill.value)}\n`;
+ }
+ });
+ return {
+ total: this.state.total,
+ moneyDetailsNotes,
+ moneyDetails: { ...this.state.moneyDetails },
+ };
+ }
+}
- MoneyDetailsPopup.template = 'MoneyDetailsPopup';
- Registries.Component.add(MoneyDetailsPopup);
+MoneyDetailsPopup.template = "MoneyDetailsPopup";
+Registries.Component.add(MoneyDetailsPopup);
- return MoneyDetailsPopup;
-
-});
+export default MoneyDetailsPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js
index 6575cd99ee8..c20515d8839 100644
--- a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js
@@ -1,80 +1,80 @@
-odoo.define('point_of_sale.NumberPopup', function(require) {
- 'use strict';
- var core = require('web.core');
- var _t = core._t;
+/** @odoo-module */
+import core from "web.core";
+var _t = core._t;
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const NumberBuffer = require('point_of_sale.NumberBuffer');
- const { useListener } = require("@web/core/utils/hooks");
- const Registries = require('point_of_sale.Registries');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
- const { useState } = owl;
+const { useState } = owl;
- // formerly NumberPopupWidget
- class NumberPopup extends AbstractAwaitablePopup {
- /**
- * @param {Object} props
- * @param {Boolean} props.isPassword Show password popup.
- * @param {number|null} props.startingValue Starting value of the popup.
- * @param {Boolean} props.isInputSelected Input is highlighted and will reset upon a change.
- *
- * Resolve to { confirmed, payload } when used with showPopup method.
- * @confirmed {Boolean}
- * @payload {String}
- */
- setup() {
- super.setup();
- useListener('accept-input', this.confirm);
- useListener('close-this-popup', this.cancel);
- let startingBuffer = '';
- if (typeof this.props.startingValue === 'number' && this.props.startingValue > 0) {
- startingBuffer = this.props.startingValue.toString().replace('.', this.decimalSeparator);
- }
- this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected });
- NumberBuffer.use({
- nonKeyboardInputEvent: 'numpad-click-input',
- triggerAtEnter: 'accept-input',
- triggerAtEscape: 'close-this-popup',
- state: this.state,
- });
+// formerly NumberPopupWidget
+class NumberPopup extends AbstractAwaitablePopup {
+ /**
+ * @param {Object} props
+ * @param {Boolean} props.isPassword Show password popup.
+ * @param {number|null} props.startingValue Starting value of the popup.
+ * @param {Boolean} props.isInputSelected Input is highlighted and will reset upon a change.
+ *
+ * Resolve to { confirmed, payload } when used with showPopup method.
+ * @confirmed {Boolean}
+ * @payload {String}
+ */
+ setup() {
+ super.setup();
+ useListener("accept-input", this.confirm);
+ useListener("close-this-popup", this.cancel);
+ let startingBuffer = "";
+ if (typeof this.props.startingValue === "number" && this.props.startingValue > 0) {
+ startingBuffer = this.props.startingValue
+ .toString()
+ .replace(".", this.decimalSeparator);
}
- get decimalSeparator() {
- return this.env._t.database.parameters.decimal_point;
+ this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected });
+ NumberBuffer.use({
+ nonKeyboardInputEvent: "numpad-click-input",
+ triggerAtEnter: "accept-input",
+ triggerAtEscape: "close-this-popup",
+ state: this.state,
+ });
+ }
+ get decimalSeparator() {
+ return this.env._t.database.parameters.decimal_point;
+ }
+ get inputBuffer() {
+ if (this.state.buffer === null) {
+ return "";
}
- get inputBuffer() {
- if (this.state.buffer === null) {
- return '';
- }
- if (this.props.isPassword) {
- return this.state.buffer.replace(/./g, '•');
- } else {
- return this.state.buffer;
- }
- }
- confirm(event) {
- if (NumberBuffer.get()) {
- super.confirm();
- }
- }
- sendInput(key) {
- this.trigger('numpad-click-input', { key });
- }
- getPayload() {
- return NumberBuffer.get();
+ if (this.props.isPassword) {
+ return this.state.buffer.replace(/./g, "•");
+ } else {
+ return this.state.buffer;
}
}
- NumberPopup.template = 'NumberPopup';
- NumberPopup.defaultProps = {
- confirmText: _t('Confirm'),
- cancelText: _t('Discard'),
- title: _t('Confirm ?'),
- body: '',
- cheap: false,
- startingValue: null,
- isPassword: false,
- };
+ confirm(event) {
+ if (NumberBuffer.get()) {
+ super.confirm();
+ }
+ }
+ sendInput(key) {
+ this.trigger("numpad-click-input", { key });
+ }
+ getPayload() {
+ return NumberBuffer.get();
+ }
+}
+NumberPopup.template = "NumberPopup";
+NumberPopup.defaultProps = {
+ confirmText: _t("Confirm"),
+ cancelText: _t("Discard"),
+ title: _t("Confirm ?"),
+ body: "",
+ cheap: false,
+ startingValue: null,
+ isPassword: false,
+};
- Registries.Component.add(NumberPopup);
+Registries.Component.add(NumberPopup);
- return NumberPopup;
-});
+export default NumberPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js b/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js
index 5da85994243..e8d878a720e 100644
--- a/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/OfflineErrorPopup.js
@@ -1,30 +1,28 @@
-odoo.define('point_of_sale.OfflineErrorPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const ErrorPopup = require('point_of_sale.ErrorPopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import ErrorPopup from "@point_of_sale/js/Popups/ErrorPopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- /**
- * This is a special kind of error popup as it introduces
- * an option to not show it again.
- */
- class OfflineErrorPopup extends ErrorPopup {
- dontShowAgain() {
- this.constructor.dontShow = true;
- this.cancel();
- }
+/**
+ * This is a special kind of error popup as it introduces
+ * an option to not show it again.
+ */
+class OfflineErrorPopup extends ErrorPopup {
+ dontShowAgain() {
+ this.constructor.dontShow = true;
+ this.cancel();
}
- OfflineErrorPopup.template = 'OfflineErrorPopup';
- OfflineErrorPopup.dontShow = false;
- OfflineErrorPopup.defaultProps = {
- confirmText: _lt('Ok'),
- cancelText: _lt('Cancel'),
- title: _lt('Offline Error'),
- body: _lt('Either the server is inaccessible or browser is not connected online.'),
- };
+}
+OfflineErrorPopup.template = "OfflineErrorPopup";
+OfflineErrorPopup.dontShow = false;
+OfflineErrorPopup.defaultProps = {
+ confirmText: _lt("Ok"),
+ cancelText: _lt("Cancel"),
+ title: _lt("Offline Error"),
+ body: _lt("Either the server is inaccessible or browser is not connected online."),
+};
- Registries.Component.add(OfflineErrorPopup);
+Registries.Component.add(OfflineErrorPopup);
- return OfflineErrorPopup;
-});
+export default OfflineErrorPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/OrderImportPopup.js b/addons/point_of_sale/static/src/js/Popups/OrderImportPopup.js
index 951957819a5..aa16638428b 100644
--- a/addons/point_of_sale/static/src/js/Popups/OrderImportPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/OrderImportPopup.js
@@ -1,28 +1,26 @@
-odoo.define('point_of_sale.OrderImportPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- // formerly OrderImportPopupWidget
- class OrderImportPopup extends AbstractAwaitablePopup {
- get unpaidSkipped() {
- return (
- (this.props.report.unpaid_skipped_existing || 0) +
- (this.props.report.unpaid_skipped_session || 0)
- );
- }
- getPayload() {}
+// formerly OrderImportPopupWidget
+class OrderImportPopup extends AbstractAwaitablePopup {
+ get unpaidSkipped() {
+ return (
+ (this.props.report.unpaid_skipped_existing || 0) +
+ (this.props.report.unpaid_skipped_session || 0)
+ );
}
- OrderImportPopup.template = 'OrderImportPopup';
- OrderImportPopup.defaultProps = {
- confirmText: _lt('Ok'),
- cancelKey: false,
- body: '',
- };
+ getPayload() {}
+}
+OrderImportPopup.template = "OrderImportPopup";
+OrderImportPopup.defaultProps = {
+ confirmText: _lt("Ok"),
+ cancelKey: false,
+ body: "",
+};
- Registries.Component.add(OrderImportPopup);
+Registries.Component.add(OrderImportPopup);
- return OrderImportPopup;
-});
+export default OrderImportPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/PosPopupController.js b/addons/point_of_sale/static/src/js/Popups/PosPopupController.js
index dc47d155ab5..9e01e95f02e 100644
--- a/addons/point_of_sale/static/src/js/Popups/PosPopupController.js
+++ b/addons/point_of_sale/static/src/js/Popups/PosPopupController.js
@@ -1,123 +1,126 @@
-odoo.define('point_of_sale.PosPopupController', function(require) {
- 'use strict';
+/** @odoo-module */
- const Registries = require('point_of_sale.Registries');
- const PosComponent = require('point_of_sale.PosComponent');
- const { useBus } = require('@web/core/utils/hooks');
+import Registries from "@point_of_sale/js/Registries";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import { useBus } from "@web/core/utils/hooks";
- /**
- * This component is responsible in controlling the popups. It does so
- * by coordinating with them thru the `env.posbus`. The basic steps follow:
- * 1. `showPopup` method triggers `show-popup` event resulting to the
- * mounting of the requested popup.
- * 2. When the popup is shown, the `confirm`/`cancel` method of the popup
- * will be called after the popup is used. `confirming`/`cancelling`
- * will trigger the `close-popup`, which this component also listens to,
- * resulting to closing of the popup.
- *
- * Furthermore, Pressing `confirmKey`/`cancelKey` which defaults to
- * 'Enter'/'Escape', will automatically `confirm`/`cancel` the `topPopup`.
- * This behavior is accomplished by listening to `keyup` event of the window.
- * When the `confirmKey`/`cancelKey` of the `topPopup` is pressed,
- * 'cancel-popup-{top-popup-id}'/'confirm-popup-{top-popup-id}' will be triggered
- * and since the popup is listening to that event (@see AbstractAwaitablePopup),
- * it will result to the call of `confirm`/`cancel` method.
- *
- * @typedef {{ id: number, resolve: Function, keepBehind?: boolean, cancelKey?: string, confirmKey?: string }} BasePopupProps
- * @typedef {{ name: string, component: AbstractAwaitablePopup, props: BasePopupProps, key: string }} Popup
- */
- class PosPopupController extends PosComponent {
- setup() {
- super.setup();
- useBus(this.env.posbus, 'show-popup', this._showPopup);
- useBus(this.env.posbus, 'close-popup', this._closePopup);
- owl.useExternalListener(window, 'keyup', this._onWindowKeyup);
- this.popups = owl.useState([]);
- }
- _showPopup(event) {
- let { id, name, props, resolve } = event.detail;
- props = Object.assign(props || {}, { id, resolve });
- const component = this.constructor.components[name];
- if (!component) {
- throw new Error(`'${name}' is not found. Make sure the file is loaded and the component is properly registered using 'Registries.Component.add'.`);
- }
- if (component.dontShow) {
- resolve();
- return;
- }
- this.popups.push({
- name,
- component,
- props: this._constructPopupProps(component, props),
- key: `${name}-${id}`,
- });
- }
- _closePopup(event) {
- const { popupId, response } = event.detail;
- const index = this.popups.findIndex((popup) => popup.props.id == popupId);
- if (index != -1) {
- const popup = this.popups[index];
- popup.props.resolve(response);
- this.popups.splice(index, 1);
- }
- }
- _onWindowKeyup(event) {
- const eventIsFromInputField = event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA';
- const shouldHandleKey = this.topPopup && !eventIsFromInputField;
- if (!shouldHandleKey) return;
-
- if (event.key === this.topPopup.props.cancelKey) {
- this.env.posbus.trigger(`cancel-popup-${this.topPopup.props.id}`);
- } else if (event.key === this.topPopup.props.confirmKey) {
- this.env.posbus.trigger(`confirm-popup-${this.topPopup.props.id}`);
- }
- }
- /**
- * A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default.
- * Also, if it's not the top popup, it is hidden from the view.
- * This can be overridden by the default props of the popop component
- * and the props used in requesting to show the popup.
- *
- * @param {AbstractAwaitablePopup} popupComponent
- * @param {Object} props
- * @returns {BasePopupProps}
- */
- _constructPopupProps(popupComponent, props) {
- const defaultProps = popupComponent.defaultProps || {};
- return Object.assign(
- {
- keepBehind: false,
- cancelKey: 'Escape',
- confirmKey: 'Enter',
- },
- defaultProps,
- props
+/**
+ * This component is responsible in controlling the popups. It does so
+ * by coordinating with them thru the `env.posbus`. The basic steps follow:
+ * 1. `showPopup` method triggers `show-popup` event resulting to the
+ * mounting of the requested popup.
+ * 2. When the popup is shown, the `confirm`/`cancel` method of the popup
+ * will be called after the popup is used. `confirming`/`cancelling`
+ * will trigger the `close-popup`, which this component also listens to,
+ * resulting to closing of the popup.
+ *
+ * Furthermore, Pressing `confirmKey`/`cancelKey` which defaults to
+ * 'Enter'/'Escape', will automatically `confirm`/`cancel` the `topPopup`.
+ * This behavior is accomplished by listening to `keyup` event of the window.
+ * When the `confirmKey`/`cancelKey` of the `topPopup` is pressed,
+ * 'cancel-popup-{top-popup-id}'/'confirm-popup-{top-popup-id}' will be triggered
+ * and since the popup is listening to that event (@see AbstractAwaitablePopup),
+ * it will result to the call of `confirm`/`cancel` method.
+ *
+ * @typedef {{ id: number, resolve: Function, keepBehind?: boolean, cancelKey?: string, confirmKey?: string }} BasePopupProps
+ * @typedef {{ name: string, component: AbstractAwaitablePopup, props: BasePopupProps, key: string }} Popup
+ */
+class PosPopupController extends PosComponent {
+ setup() {
+ super.setup();
+ useBus(this.env.posbus, "show-popup", this._showPopup);
+ useBus(this.env.posbus, "close-popup", this._closePopup);
+ owl.useExternalListener(window, "keyup", this._onWindowKeyup);
+ this.popups = owl.useState([]);
+ }
+ _showPopup(event) {
+ let { id, name, props, resolve } = event.detail;
+ props = Object.assign(props || {}, { id, resolve });
+ const component = this.constructor.components[name];
+ if (!component) {
+ throw new Error(
+ `'${name}' is not found. Make sure the file is loaded and the component is properly registered using 'Registries.Component.add'.`
);
}
- /**
- * @returns {boolean} Hide the element of this component when this returns false.
- */
- isShown() {
- return this.popups.length > 0;
+ if (component.dontShow) {
+ resolve();
+ return;
}
- get topPopup() {
- return this.popups[this.popups.length - 1];
- }
- /**
- * By default, only show the top popup. But always show a popup if
- * `keepBehind` props is true. Meaning, if you have 2 popups, and
- * the bottom popup has `keepBehind = true`, then the bottom popup
- * will be visible if it's not blocked in the view by the top popup.
- *
- * @param {Popup} popup
- * @returns {boolean}
- */
- shouldShow(popup) {
- return this.topPopup === popup || popup.props.keepBehind;
+ this.popups.push({
+ name,
+ component,
+ props: this._constructPopupProps(component, props),
+ key: `${name}-${id}`,
+ });
+ }
+ _closePopup(event) {
+ const { popupId, response } = event.detail;
+ const index = this.popups.findIndex((popup) => popup.props.id == popupId);
+ if (index != -1) {
+ const popup = this.popups[index];
+ popup.props.resolve(response);
+ this.popups.splice(index, 1);
}
}
- PosPopupController.template = 'point_of_sale.PosPopupController';
- Registries.Component.add(PosPopupController);
+ _onWindowKeyup(event) {
+ const eventIsFromInputField =
+ event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA";
+ const shouldHandleKey = this.topPopup && !eventIsFromInputField;
+ if (!shouldHandleKey) {
+ return;
+ }
- return PosPopupController;
-});
+ if (event.key === this.topPopup.props.cancelKey) {
+ this.env.posbus.trigger(`cancel-popup-${this.topPopup.props.id}`);
+ } else if (event.key === this.topPopup.props.confirmKey) {
+ this.env.posbus.trigger(`confirm-popup-${this.topPopup.props.id}`);
+ }
+ }
+ /**
+ * A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default.
+ * Also, if it's not the top popup, it is hidden from the view.
+ * This can be overridden by the default props of the popop component
+ * and the props used in requesting to show the popup.
+ *
+ * @param {AbstractAwaitablePopup} popupComponent
+ * @param {Object} props
+ * @returns {BasePopupProps}
+ */
+ _constructPopupProps(popupComponent, props) {
+ const defaultProps = popupComponent.defaultProps || {};
+ return Object.assign(
+ {
+ keepBehind: false,
+ cancelKey: "Escape",
+ confirmKey: "Enter",
+ },
+ defaultProps,
+ props
+ );
+ }
+ /**
+ * @returns {boolean} Hide the element of this component when this returns false.
+ */
+ isShown() {
+ return this.popups.length > 0;
+ }
+ get topPopup() {
+ return this.popups[this.popups.length - 1];
+ }
+ /**
+ * By default, only show the top popup. But always show a popup if
+ * `keepBehind` props is true. Meaning, if you have 2 popups, and
+ * the bottom popup has `keepBehind = true`, then the bottom popup
+ * will be visible if it's not blocked in the view by the top popup.
+ *
+ * @param {Popup} popup
+ * @returns {boolean}
+ */
+ shouldShow(popup) {
+ return this.topPopup === popup || popup.props.keepBehind;
+ }
+}
+PosPopupController.template = "point_of_sale.PosPopupController";
+Registries.Component.add(PosPopupController);
+
+export default PosPopupController;
diff --git a/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js b/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js
index ab9928a1ae5..cb00c72f03f 100644
--- a/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js
@@ -1,93 +1,84 @@
-odoo.define('point_of_sale.ProductConfiguratorPopup', function(require) {
- 'use strict';
+/** @odoo-module */
+import PosComponent from "@point_of_sale/js/PosComponent";
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
- const PosComponent = require('point_of_sale.PosComponent');
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
+const { useState, useSubEnv } = owl;
- const { useState, useSubEnv } = owl;
-
- class ProductConfiguratorPopup extends AbstractAwaitablePopup {
- setup() {
- super.setup();
- useSubEnv({ attribute_components: [] });
- }
-
- getPayload() {
- var selected_attributes = [];
- var price_extra = 0.0;
-
- this.env.attribute_components.forEach((attribute_component) => {
- let { value, extra } = attribute_component.getValue();
- selected_attributes.push(value);
- price_extra += extra;
- });
-
- return {
- selected_attributes,
- price_extra,
- };
- }
- }
- ProductConfiguratorPopup.template = 'ProductConfiguratorPopup';
- Registries.Component.add(ProductConfiguratorPopup);
-
- class BaseProductAttribute extends PosComponent {
- setup() {
- super.setup();
- this.env.attribute_components.push(this);
-
- this.attribute = this.props.attribute;
- this.values = this.attribute.values;
- this.state = useState({
- selected_value: parseFloat(this.values[0].id),
- custom_value: '',
- });
- }
-
- getValue() {
- let selected_value = this.values.find((val) => val.id === parseFloat(this.state.selected_value));
- let value = selected_value.name;
- if (selected_value.is_custom && this.state.custom_value) {
- value += `: ${this.state.custom_value}`;
- }
-
- return {
- value,
- extra: selected_value.price_extra
- };
- }
+export class ProductConfiguratorPopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ useSubEnv({ attribute_components: [] });
}
- class RadioProductAttribute extends BaseProductAttribute {
- 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.
- // We then manually select the first input of each radio attribute.
- $(this.el).find('input[type="radio"]:first').prop('checked', true);
- }
+ getPayload() {
+ var selected_attributes = [];
+ var price_extra = 0.0;
+
+ this.env.attribute_components.forEach((attribute_component) => {
+ const { value, extra } = attribute_component.getValue();
+ selected_attributes.push(value);
+ price_extra += extra;
+ });
+
+ return {
+ selected_attributes,
+ price_extra,
+ };
}
- RadioProductAttribute.template = 'RadioProductAttribute';
- Registries.Component.add(RadioProductAttribute);
+}
+ProductConfiguratorPopup.template = "ProductConfiguratorPopup";
+Registries.Component.add(ProductConfiguratorPopup);
- class SelectProductAttribute extends BaseProductAttribute { }
- SelectProductAttribute.template = 'SelectProductAttribute';
- Registries.Component.add(SelectProductAttribute);
+export class BaseProductAttribute extends PosComponent {
+ setup() {
+ super.setup();
+ this.env.attribute_components.push(this);
- class ColorProductAttribute extends BaseProductAttribute {}
- ColorProductAttribute.template = 'ColorProductAttribute';
- Registries.Component.add(ColorProductAttribute);
+ this.attribute = this.props.attribute;
+ this.values = this.attribute.values;
+ this.state = useState({
+ selected_value: parseFloat(this.values[0].id),
+ custom_value: "",
+ });
+ }
- return {
- ProductConfiguratorPopup,
- BaseProductAttribute,
- RadioProductAttribute,
- SelectProductAttribute,
- ColorProductAttribute,
- };
-});
+ getValue() {
+ const selected_value = this.values.find(
+ (val) => val.id === parseFloat(this.state.selected_value)
+ );
+ let value = selected_value.name;
+ if (selected_value.is_custom && this.state.custom_value) {
+ value += `: ${this.state.custom_value}`;
+ }
+
+ return {
+ value,
+ extra: selected_value.price_extra,
+ };
+ }
+}
+
+export class RadioProductAttribute extends BaseProductAttribute {
+ 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.
+ // We then manually select the first input of each radio attribute.
+ $(this.el).find('input[type="radio"]:first').prop("checked", true);
+ }
+}
+RadioProductAttribute.template = "RadioProductAttribute";
+Registries.Component.add(RadioProductAttribute);
+
+export class SelectProductAttribute extends BaseProductAttribute {}
+SelectProductAttribute.template = "SelectProductAttribute";
+Registries.Component.add(SelectProductAttribute);
+
+export class ColorProductAttribute extends BaseProductAttribute {}
+ColorProductAttribute.template = "ColorProductAttribute";
+Registries.Component.add(ColorProductAttribute);
diff --git a/addons/point_of_sale/static/src/js/Popups/ProductInfoPopup.js b/addons/point_of_sale/static/src/js/Popups/ProductInfoPopup.js
index ac04fc4dc05..9e7b9ce05c3 100644
--- a/addons/point_of_sale/static/src/js/Popups/ProductInfoPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ProductInfoPopup.js
@@ -1,32 +1,31 @@
-odoo.define('point_of_sale.ProductInfoPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
- /**
- * Props:
- * {
- * info: {object of data}
- * }
- */
- class ProductInfoPopup extends AbstractAwaitablePopup {
- setup() {
- super.setup();
- Object.assign(this, this.props.info);
- }
- searchProduct(productName) {
- this.env.posbus.trigger('search-product-from-info-popup', productName);
- this.cancel()
- }
- _hasMarginsCostsAccessRights() {
- const isAccessibleToEveryUser = this.env.pos.config.is_margins_costs_accessible_to_every_user;
- const isCashierManager = this.env.pos.get_cashier().role === 'manager';
- return isAccessibleToEveryUser || isCashierManager;
- }
+/**
+ * Props:
+ * {
+ * info: {object of data}
+ * }
+ */
+class ProductInfoPopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ Object.assign(this, this.props.info);
}
+ searchProduct(productName) {
+ this.env.posbus.trigger("search-product-from-info-popup", productName);
+ this.cancel();
+ }
+ _hasMarginsCostsAccessRights() {
+ const isAccessibleToEveryUser =
+ this.env.pos.config.is_margins_costs_accessible_to_every_user;
+ const isCashierManager = this.env.pos.get_cashier().role === "manager";
+ return isAccessibleToEveryUser || isCashierManager;
+ }
+}
- ProductInfoPopup.template = 'ProductInfoPopup';
- ProductInfoPopup.defaultProps= { confirmKey: false };
- Registries.Component.add(ProductInfoPopup);
-});
+ProductInfoPopup.template = "ProductInfoPopup";
+ProductInfoPopup.defaultProps = { confirmKey: false };
+Registries.Component.add(ProductInfoPopup);
diff --git a/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js b/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js
index c74e0fc9c90..124b53ec395 100644
--- a/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js
@@ -1,59 +1,57 @@
-odoo.define('point_of_sale.SelectionPopup', function (require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- const { useState } = owl;
+const { useState } = owl;
- // formerly SelectionPopupWidget
- class SelectionPopup extends AbstractAwaitablePopup {
- /**
- * Value of the `item` key of the selected element in the Selection
- * Array is the payload of this popup.
- *
- * @param {Object} props
- * @param {String} [props.confirmText='Confirm']
- * @param {String} [props.cancelText='Cancel']
- * @param {String} [props.title='Select']
- * @param {String} [props.body='']
- * @param {Array} [props.list=[]]
- * Selection {
- * id: integer,
- * label: string,
- * isSelected: boolean,
- * item: any,
- * }
- */
- setup() {
- super.setup();
- this.state = useState({ selectedId: this.props.list.find((item) => item.isSelected) });
- }
- selectItem(itemId) {
- this.state.selectedId = itemId;
- this.confirm();
- }
- /**
- * We send as payload of the response the selected item.
- *
- * @override
- */
- getPayload() {
- const selected = this.props.list.find((item) => this.state.selectedId === item.id);
- return selected && selected.item;
- }
+// formerly SelectionPopupWidget
+class SelectionPopup extends AbstractAwaitablePopup {
+ /**
+ * Value of the `item` key of the selected element in the Selection
+ * Array is the payload of this popup.
+ *
+ * @param {Object} props
+ * @param {String} [props.confirmText='Confirm']
+ * @param {String} [props.cancelText='Cancel']
+ * @param {String} [props.title='Select']
+ * @param {String} [props.body='']
+ * @param {Array} [props.list=[]]
+ * Selection {
+ * id: integer,
+ * label: string,
+ * isSelected: boolean,
+ * item: any,
+ * }
+ */
+ setup() {
+ super.setup();
+ this.state = useState({ selectedId: this.props.list.find((item) => item.isSelected) });
}
- SelectionPopup.template = 'SelectionPopup';
- SelectionPopup.defaultProps = {
- cancelText: _lt('Cancel'),
- title: _lt('Select'),
- body: '',
- list: [],
- confirmKey: false,
- };
+ selectItem(itemId) {
+ this.state.selectedId = itemId;
+ this.confirm();
+ }
+ /**
+ * We send as payload of the response the selected item.
+ *
+ * @override
+ */
+ getPayload() {
+ const selected = this.props.list.find((item) => this.state.selectedId === item.id);
+ return selected && selected.item;
+ }
+}
+SelectionPopup.template = "SelectionPopup";
+SelectionPopup.defaultProps = {
+ cancelText: _lt("Cancel"),
+ title: _lt("Select"),
+ body: "",
+ list: [],
+ confirmKey: false,
+};
- Registries.Component.add(SelectionPopup);
+Registries.Component.add(SelectionPopup);
- return SelectionPopup;
-});
+export default SelectionPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js b/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js
index 10e1efe376a..42123d93933 100644
--- a/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js
@@ -1,42 +1,40 @@
-odoo.define('point_of_sale.TextAreaPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- const { onMounted, useRef, useState } = owl;
+const { onMounted, useRef, useState } = owl;
- // formerly TextAreaPopupWidget
- // IMPROVEMENT: This code is very similar to TextInputPopup.
- // Combining them would reduce the code.
- class TextAreaPopup extends AbstractAwaitablePopup {
- /**
- * @param {Object} props
- * @param {string} props.startingValue
- */
- setup() {
- super.setup();
- this.state = useState({ inputValue: this.props.startingValue });
- this.inputRef = useRef('input');
- onMounted(this.onMounted);
- }
- onMounted() {
- this.inputRef.el.focus();
- }
- getPayload() {
- return this.state.inputValue;
- }
+// formerly TextAreaPopupWidget
+// IMPROVEMENT: This code is very similar to TextInputPopup.
+// Combining them would reduce the code.
+class TextAreaPopup extends AbstractAwaitablePopup {
+ /**
+ * @param {Object} props
+ * @param {string} props.startingValue
+ */
+ setup() {
+ super.setup();
+ this.state = useState({ inputValue: this.props.startingValue });
+ this.inputRef = useRef("input");
+ onMounted(this.onMounted);
}
- TextAreaPopup.template = 'TextAreaPopup';
- TextAreaPopup.defaultProps = {
- confirmText: _lt('Add'),
- cancelText: _lt('Discard'),
- title: '',
- body: '',
- };
+ onMounted() {
+ this.inputRef.el.focus();
+ }
+ getPayload() {
+ return this.state.inputValue;
+ }
+}
+TextAreaPopup.template = "TextAreaPopup";
+TextAreaPopup.defaultProps = {
+ confirmText: _lt("Add"),
+ cancelText: _lt("Discard"),
+ title: "",
+ body: "",
+};
- Registries.Component.add(TextAreaPopup);
+Registries.Component.add(TextAreaPopup);
- return TextAreaPopup;
-});
+export default TextAreaPopup;
diff --git a/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js b/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js
index d7188c69f60..26aa028f40d 100644
--- a/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js
@@ -1,38 +1,36 @@
-odoo.define('point_of_sale.TextInputPopup', function(require) {
- 'use strict';
+/** @odoo-module */
- const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
- const Registries = require('point_of_sale.Registries');
- const { _lt } = require('@web/core/l10n/translation');
+import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
+import Registries from "@point_of_sale/js/Registries";
+import { _lt } from "@web/core/l10n/translation";
- const { onMounted, useRef, useState } = owl;
+const { onMounted, useRef, useState } = owl;
- // formerly TextInputPopupWidget
- class TextInputPopup extends AbstractAwaitablePopup {
- setup() {
- super.setup();
- this.state = useState({ inputValue: this.props.startingValue });
- this.inputRef = useRef('input');
- onMounted(this.onMounted);
- }
- onMounted() {
- this.inputRef.el.focus();
- }
- getPayload() {
- return this.state.inputValue;
- }
+// formerly TextInputPopupWidget
+class TextInputPopup extends AbstractAwaitablePopup {
+ setup() {
+ super.setup();
+ this.state = useState({ inputValue: this.props.startingValue });
+ this.inputRef = useRef("input");
+ onMounted(this.onMounted);
}
- TextInputPopup.template = 'TextInputPopup';
- TextInputPopup.defaultProps = {
- confirmText: _lt('Confirm'),
- cancelText: _lt('Discard'),
- title: '',
- body: '',
- startingValue: '',
- placeholder: '',
- };
+ onMounted() {
+ this.inputRef.el.focus();
+ }
+ getPayload() {
+ return this.state.inputValue;
+ }
+}
+TextInputPopup.template = "TextInputPopup";
+TextInputPopup.defaultProps = {
+ confirmText: _lt("Confirm"),
+ cancelText: _lt("Discard"),
+ title: "",
+ body: "",
+ startingValue: "",
+ placeholder: "",
+};
- Registries.Component.add(TextInputPopup);
+Registries.Component.add(TextInputPopup);
- return TextInputPopup;
-});
+export default TextInputPopup;
diff --git a/addons/point_of_sale/static/src/js/PosComponent.js b/addons/point_of_sale/static/src/js/PosComponent.js
index 2f969a687ad..26b5fc3fbe1 100644
--- a/addons/point_of_sale/static/src/js/PosComponent.js
+++ b/addons/point_of_sale/static/src/js/PosComponent.js
@@ -1,75 +1,73 @@
-odoo.define('point_of_sale.PosComponent', function (require) {
- 'use strict';
+/** @odoo-module */
- const { LegacyComponent } = require("@web/legacy/legacy_component");
- const { onRendered } = owl;
+import { LegacyComponent } from "@web/legacy/legacy_component";
+const { onRendered } = owl;
- let nextId = 0;
+let nextId = 0;
- class PosComponent extends LegacyComponent {
- 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
- * returns a response after user interaction. See the following for quick
- * demonstration:
- *
- * ```
- * async getUserName() {
- * const userResponse = await this.showPopup(
- * 'TextInputPopup',
- * { title: 'What is your name?' }
- * );
- * // at this point, the TextInputPopup is displayed. Depending on how the popup is defined,
- * // say the input contains the name, the result of the interaction with the user is
- * // saved in `userResponse`.
- * console.log(userResponse); // logs { confirmed: true, payload: }
- * }
- * ```
- *
- * @param {String} name Name of the popup component
- * @param {Object} props Object that will be used to render to popup
- */
- showPopup(name, props) {
- return new Promise((resolve) => {
- this.env.posbus.trigger('show-popup', { name, props, resolve, id: nextId++ });
- });
- }
- showTempScreen(name, props) {
- return new Promise((resolve) => {
- this.trigger('show-temp-screen', { name, props, resolve });
- });
- }
- showScreen(name, props) {
- this.trigger('show-main-screen', { name, props });
- }
- /**
- * @param {String} name 'bell' | 'error'
- */
- playSound(name) {
- this.trigger('play-sound', name);
- }
- /**
- * Control the SyncNotification component.
- * @param {String} status 'connected' | 'connecting' | 'disconnected' | 'error'
- * @param {String} pending number of pending orders to sync
- */
- setSyncStatus(status, pending) {
- this.trigger('set-sync-status', { status, pending });
- }
- showNotification(message, duration = 2000) {
- this.trigger('show-notification', { message, duration });
- }
- closeNotification() {
- this.trigger('close-notification');
- }
+class PosComponent extends LegacyComponent {
+ 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
+ * returns a response after user interaction. See the following for quick
+ * demonstration:
+ *
+ * ```
+ * async getUserName() {
+ * const userResponse = await this.showPopup(
+ * 'TextInputPopup',
+ * { title: 'What is your name?' }
+ * );
+ * // at this point, the TextInputPopup is displayed. Depending on how the popup is defined,
+ * // say the input contains the name, the result of the interaction with the user is
+ * // saved in `userResponse`.
+ * console.log(userResponse); // logs { confirmed: true, payload: }
+ * }
+ * ```
+ *
+ * @param {String} name Name of the popup component
+ * @param {Object} props Object that will be used to render to popup
+ */
+ showPopup(name, props) {
+ return new Promise((resolve) => {
+ this.env.posbus.trigger("show-popup", { name, props, resolve, id: nextId++ });
+ });
+ }
+ showTempScreen(name, props) {
+ return new Promise((resolve) => {
+ this.trigger("show-temp-screen", { name, props, resolve });
+ });
+ }
+ showScreen(name, props) {
+ this.trigger("show-main-screen", { name, props });
+ }
+ /**
+ * @param {String} name 'bell' | 'error'
+ */
+ playSound(name) {
+ this.trigger("play-sound", name);
+ }
+ /**
+ * Control the SyncNotification component.
+ * @param {String} status 'connected' | 'connecting' | 'disconnected' | 'error'
+ * @param {String} pending number of pending orders to sync
+ */
+ setSyncStatus(status, pending) {
+ this.trigger("set-sync-status", { status, pending });
+ }
+ showNotification(message, duration = 2000) {
+ this.trigger("show-notification", { message, duration });
+ }
+ closeNotification() {
+ this.trigger("close-notification");
+ }
+}
- return PosComponent;
-});
+export default PosComponent;
diff --git a/addons/point_of_sale/static/src/js/PosContext.js b/addons/point_of_sale/static/src/js/PosContext.js
index cf6ce355160..677ebdad04e 100644
--- a/addons/point_of_sale/static/src/js/PosContext.js
+++ b/addons/point_of_sale/static/src/js/PosContext.js
@@ -1,10 +1,6 @@
-odoo.define('point_of_sale.PosContext', function (require) {
- 'use strict';
- const { reactive } = owl;
+/** @odoo-module */
+const { reactive } = owl;
- // Create global context objects
- // e.g. component.env.device = new Context({ isMobile: false });
- return {
- orderManagement: reactive({ searchString: '', selectedOrder: null }),
- };
-});
+// Create global context objects
+// e.g. component.env.device = new Context({ isMobile: false });
+export const orderManagement = reactive({ searchString: "", selectedOrder: null });
diff --git a/addons/point_of_sale/static/src/js/Registries.js b/addons/point_of_sale/static/src/js/Registries.js
index e88d23d6c57..b55a3424f9a 100644
--- a/addons/point_of_sale/static/src/js/Registries.js
+++ b/addons/point_of_sale/static/src/js/Registries.js
@@ -1,27 +1,27 @@
-odoo.define('point_of_sale.Registries', function(require) {
- 'use strict';
+/** @odoo-module */
+/**
+ * This definition contains all the instances of ClassRegistry.
+ */
- /**
- * This definition contains all the instances of ClassRegistry.
- */
+import ComponentRegistry from "@point_of_sale/js/ComponentRegistry";
+import ClassRegistry from "@point_of_sale/js/ClassRegistry";
- const ComponentRegistry = require('point_of_sale.ComponentRegistry');
- const ClassRegistry = require('point_of_sale.ClassRegistry');
-
- class ModelRegistry extends ClassRegistry {
- add(baseClass) {
- super.add(baseClass);
- /**
- * Introduce a static method (`create`) to each base class that can be
- * conveniently use to create an instance of the extended version
- * of the class.
- */
- baseClass.create = (...args) => {
- const ExtendedClass = this.get(baseClass);
- return new ExtendedClass(...args);
- }
- }
+class ModelRegistry extends ClassRegistry {
+ add(baseClass) {
+ super.add(baseClass);
+ /**
+ * Introduce a static method (`create`) to each base class that can be
+ * conveniently use to create an instance of the extended version
+ * of the class.
+ */
+ baseClass.create = (...args) => {
+ const ExtendedClass = this.get(baseClass);
+ return new ExtendedClass(...args);
+ };
}
+}
- return { Component: new ComponentRegistry(), Model: new ModelRegistry() };
-});
+export const Component = new ComponentRegistry();
+export const Model = new ModelRegistry();
+
+export default { Component, Model };
diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js
index 78403b1721c..24e969f0604 100644
--- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js
+++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js
@@ -1,135 +1,131 @@
-odoo.define('point_of_sale.PartnerDetailsEdit', function(require) {
- 'use strict';
+/** @odoo-module */
- const { _t } = require('web.core');
- const { getDataURLFromFile } = require('web.utils');
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { _t } from "web.core";
+import { getDataURLFromFile } from "web.utils";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { onMounted, onWillUnmount } = owl;
+const { onMounted, onWillUnmount } = owl;
- class PartnerDetailsEdit extends PosComponent {
- 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],
- };
- if (!partner.property_product_pricelist)
- this.changes['property_product_pricelist'] = this.env.pos.default_pricelist.id;
-
- onMounted(() => {
- this.env.bus.on('save-partner', this, this.saveChanges);
- });
-
- onWillUnmount(() => {
- this.env.bus.off('save-partner', this);
- });
+class PartnerDetailsEdit extends PosComponent {
+ 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],
+ };
+ if (!partner.property_product_pricelist) {
+ this.changes["property_product_pricelist"] = this.env.pos.default_pricelist.id;
}
- get partnerImageUrl() {
- // We prioritize image_1920 in the `changes` field because we want
- // to show the uploaded image without fetching new data from the server.
- const partner = this.props.partner;
- if (this.changes.image_1920) {
- return this.changes.image_1920;
- } else if (partner.id) {
- return `/web/image?model=res.partner&id=${partner.id}&field=avatar_128&unique=${partner.write_date}`;
- } else {
- return false;
- }
- }
- /**
- * Save to field `changes` all input changes from the form fields.
- */
- captureChange(event) {
- this.changes[event.target.name] = event.target.value;
- }
- saveChanges() {
- let processedChanges = {};
- for (let [key, value] of Object.entries(this.changes)) {
- if (this.intFields.includes(key)) {
- processedChanges[key] = parseInt(value) || false;
- } else {
- processedChanges[key] = value;
- }
- }
- if ((!this.props.partner.name && !processedChanges.name) ||
- processedChanges.name === '' ){
- return this.showPopup('ErrorPopup', {
- title: _t('A Customer Name Is Required'),
- });
- }
- processedChanges.id = this.props.partner.id || false;
- this.trigger('save-changes', { processedChanges });
- }
- async uploadImage(event) {
- const file = event.target.files[0];
- if (!file.type.match(/image.*/)) {
- await this.showPopup('ErrorPopup', {
- title: this.env._t('Unsupported File Format'),
- body: this.env._t(
- 'Only web-compatible Image formats such as .png or .jpeg are supported.'
- ),
- });
- } else {
- const imageUrl = await getDataURLFromFile(file);
- const loadedImage = await this._loadImage(imageUrl);
- if (loadedImage) {
- const resizedImage = await this._resizeImage(loadedImage, 800, 600);
- this.changes.image_1920 = resizedImage.toDataURL();
- // Rerender to reflect the changes in the screen
- this.render(true);
- }
- }
- }
- _resizeImage(img, maxwidth, maxheight) {
- var canvas = document.createElement('canvas');
- var ctx = canvas.getContext('2d');
- var ratio = 1;
- if (img.width > maxwidth) {
- ratio = maxwidth / img.width;
- }
- if (img.height * ratio > maxheight) {
- ratio = maxheight / img.height;
- }
- var width = Math.floor(img.width * ratio);
- var height = Math.floor(img.height * ratio);
+ onMounted(() => {
+ this.env.bus.on("save-partner", this, this.saveChanges);
+ });
- canvas.width = width;
- canvas.height = height;
- ctx.drawImage(img, 0, 0, width, height);
- return canvas;
- }
- /**
- * Loading image is converted to a Promise to allow await when
- * loading an image. It resolves to the loaded image if succesful,
- * else, resolves to false.
- *
- * [Source](https://stackoverflow.com/questions/45788934/how-to-turn-this-callback-into-a-promise-using-async-await)
- */
- _loadImage(url) {
- return new Promise((resolve) => {
- const img = new Image();
- img.addEventListener('load', () => resolve(img));
- img.addEventListener('error', () => {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Loading Image Error'),
- body: this.env._t(
- 'Encountered error when loading image. Please try again.'
- ),
- });
- resolve(false);
- });
- img.src = url;
- });
+ onWillUnmount(() => {
+ this.env.bus.off("save-partner", this);
+ });
+ }
+ get partnerImageUrl() {
+ // We prioritize image_1920 in the `changes` field because we want
+ // to show the uploaded image without fetching new data from the server.
+ const partner = this.props.partner;
+ if (this.changes.image_1920) {
+ return this.changes.image_1920;
+ } else if (partner.id) {
+ return `/web/image?model=res.partner&id=${partner.id}&field=avatar_128&unique=${partner.write_date}`;
+ } else {
+ return false;
}
}
- PartnerDetailsEdit.template = 'PartnerDetailsEdit';
+ /**
+ * Save to field `changes` all input changes from the form fields.
+ */
+ captureChange(event) {
+ this.changes[event.target.name] = event.target.value;
+ }
+ saveChanges() {
+ const processedChanges = {};
+ for (const [key, value] of Object.entries(this.changes)) {
+ if (this.intFields.includes(key)) {
+ processedChanges[key] = parseInt(value) || false;
+ } else {
+ processedChanges[key] = value;
+ }
+ }
+ if ((!this.props.partner.name && !processedChanges.name) || processedChanges.name === "") {
+ return this.showPopup("ErrorPopup", {
+ title: _t("A Customer Name Is Required"),
+ });
+ }
+ processedChanges.id = this.props.partner.id || false;
+ this.trigger("save-changes", { processedChanges });
+ }
+ async uploadImage(event) {
+ const file = event.target.files[0];
+ if (!file.type.match(/image.*/)) {
+ await this.showPopup("ErrorPopup", {
+ title: this.env._t("Unsupported File Format"),
+ body: this.env._t(
+ "Only web-compatible Image formats such as .png or .jpeg are supported."
+ ),
+ });
+ } else {
+ const imageUrl = await getDataURLFromFile(file);
+ const loadedImage = await this._loadImage(imageUrl);
+ if (loadedImage) {
+ const resizedImage = await this._resizeImage(loadedImage, 800, 600);
+ this.changes.image_1920 = resizedImage.toDataURL();
+ // Rerender to reflect the changes in the screen
+ this.render(true);
+ }
+ }
+ }
+ _resizeImage(img, maxwidth, maxheight) {
+ var canvas = document.createElement("canvas");
+ var ctx = canvas.getContext("2d");
+ var ratio = 1;
- Registries.Component.add(PartnerDetailsEdit);
+ if (img.width > maxwidth) {
+ ratio = maxwidth / img.width;
+ }
+ if (img.height * ratio > maxheight) {
+ ratio = maxheight / img.height;
+ }
+ var width = Math.floor(img.width * ratio);
+ var height = Math.floor(img.height * ratio);
- return PartnerDetailsEdit;
-});
+ canvas.width = width;
+ canvas.height = height;
+ ctx.drawImage(img, 0, 0, width, height);
+ return canvas;
+ }
+ /**
+ * Loading image is converted to a Promise to allow await when
+ * loading an image. It resolves to the loaded image if succesful,
+ * else, resolves to false.
+ *
+ * [Source](https://stackoverflow.com/questions/45788934/how-to-turn-this-callback-into-a-promise-using-async-await)
+ */
+ _loadImage(url) {
+ return new Promise((resolve) => {
+ const img = new Image();
+ img.addEventListener("load", () => resolve(img));
+ img.addEventListener("error", () => {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Loading Image Error"),
+ body: this.env._t("Encountered error when loading image. Please try again."),
+ });
+ resolve(false);
+ });
+ img.src = url;
+ });
+ }
+}
+PartnerDetailsEdit.template = "PartnerDetailsEdit";
+
+Registries.Component.add(PartnerDetailsEdit);
+
+export default PartnerDetailsEdit;
diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js
index 17a01a548ad..cd45d0e4660 100644
--- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js
+++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js
@@ -1,24 +1,22 @@
-odoo.define('point_of_sale.PartnerLine', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class PartnerLine extends PosComponent {
- get highlight() {
- return this._isPartnerSelected ? 'highlight' : '';
- }
- get shortAddress() {
- const { partner } = this.props;
- return [partner.zip, partner.city, partner.state_id[1]].filter(field => field).join(', ');
- }
- get _isPartnerSelected() {
- return this.props.partner === this.props.selectedPartner;
- }
+class PartnerLine extends PosComponent {
+ get highlight() {
+ return this._isPartnerSelected ? "highlight" : "";
}
- PartnerLine.template = 'PartnerLine';
+ get shortAddress() {
+ const { partner } = this.props;
+ return [partner.zip, partner.city, partner.state_id[1]].filter((field) => field).join(", ");
+ }
+ get _isPartnerSelected() {
+ return this.props.partner === this.props.selectedPartner;
+ }
+}
+PartnerLine.template = "PartnerLine";
- Registries.Component.add(PartnerLine);
+Registries.Component.add(PartnerLine);
- return PartnerLine;
-});
+export default PartnerLine;
diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js
index dd16a666d81..fc4a87ac6f2 100644
--- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js
@@ -1,216 +1,228 @@
-odoo.define('point_of_sale.PartnerListScreen', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
- const { isConnectionError } = require('point_of_sale.utils');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
+import { isConnectionError } from "@point_of_sale/js/utils";
- const { debounce } = require("@web/core/utils/timing");
- const { useListener } = require("@web/core/utils/hooks");
+import { debounce } from "@web/core/utils/timing";
+import { useListener } from "@web/core/utils/hooks";
- const { onWillUnmount, useRef, onMounted } = owl;
+const { onWillUnmount, useRef, onMounted } = owl;
- /**
- * Render this screen using `showTempScreen` to select partner.
- * When the shown screen is confirmed ('Set Customer' or 'Deselect Customer'
- * button is clicked), the call to `showTempScreen` resolves to the
- * selected partner. E.g.
- *
- * ```js
- * const { confirmed, payload: selectedPartner } = await showTempScreen('PartnerListScreen');
- * if (confirmed) {
- * // do something with the selectedPartner
- * }
- * ```
- *
- * @props partner - originally selected partner
- */
- class PartnerListScreen extends PosComponent {
- setup() {
- super.setup();
- useListener('click-save', () => this.env.bus.trigger('save-partner'));
- useListener('save-changes', this.saveChanges);
- this.searchWordInputRef = useRef('search-word-input-partner');
+/**
+ * Render this screen using `showTempScreen` to select partner.
+ * When the shown screen is confirmed ('Set Customer' or 'Deselect Customer'
+ * button is clicked), the call to `showTempScreen` resolves to the
+ * selected partner. E.g.
+ *
+ * ```js
+ * const { confirmed, payload: selectedPartner } = await showTempScreen('PartnerListScreen');
+ * if (confirmed) {
+ * // do something with the selectedPartner
+ * }
+ * ```
+ *
+ * @props partner - originally selected partner
+ */
+class PartnerListScreen extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click-save", () => this.env.bus.trigger("save-partner"));
+ useListener("save-changes", this.saveChanges);
+ this.searchWordInputRef = useRef("search-word-input-partner");
- // We are not using useState here because the object
- // passed to useState converts the object and its contents
- // to Observer proxy. Not sure of the side-effects of making
- // a persistent object, such as pos, into Observer. But it
- // is better to be safe.
- this.state = {
- query: null,
- selectedPartner: this.props.partner,
- detailIsShown: false,
- editModeProps: {
- partner: null,
- },
- };
- this.updatePartnerList = debounce(this.updatePartnerList, 70);
- onWillUnmount(this.updatePartnerList.cancel);
- onMounted(() => {
- if(!this.env.pos.config.limited_partners_loading)
- this.env.pos.isEveryPartnerLoaded = true;
- });
-
- }
- // Lifecycle hooks
- back() {
- if(this.state.detailIsShown) {
- this.state.detailIsShown = false;
- this.render(true);
- } else {
- this.props.resolve({ confirmed: false, payload: false });
- this.trigger('close-temp-screen');
+ // We are not using useState here because the object
+ // passed to useState converts the object and its contents
+ // to Observer proxy. Not sure of the side-effects of making
+ // a persistent object, such as pos, into Observer. But it
+ // is better to be safe.
+ this.state = {
+ query: null,
+ selectedPartner: this.props.partner,
+ detailIsShown: false,
+ editModeProps: {
+ partner: null,
+ },
+ };
+ this.updatePartnerList = debounce(this.updatePartnerList, 70);
+ onWillUnmount(this.updatePartnerList.cancel);
+ onMounted(() => {
+ if (!this.env.pos.config.limited_partners_loading) {
+ this.env.pos.isEveryPartnerLoaded = true;
}
- }
- confirm() {
- this.props.resolve({ confirmed: true, payload: this.state.selectedPartner });
- this.trigger('close-temp-screen');
- }
- activateEditMode() {
- this.state.detailIsShown = true;
+ });
+ }
+ // Lifecycle hooks
+ back() {
+ if (this.state.detailIsShown) {
+ this.state.detailIsShown = false;
this.render(true);
- }
- // Getters
-
- get currentOrder() {
- return this.env.pos.get_order();
- }
-
- get partners() {
- let res;
- if (this.state.query && this.state.query.trim() !== '') {
- res = this.env.pos.db.search_partner(this.state.query.trim());
- } else {
- res = this.env.pos.db.get_partners_sorted(1000);
- }
- res.sort(function (a, b) { return (a.name || '').localeCompare(b.name || '') });
- // the selected partner (if any) is displayed at the top of the list
- if (this.state.selectedPartner) {
- let indexOfSelectedPartner = res.findIndex( partner =>
- partner.id === this.state.selectedPartner.id
- );
- if (indexOfSelectedPartner !== -1) {
- res.splice(indexOfSelectedPartner, 1);
- res.unshift(this.state.selectedPartner);
- }
- }
- return res
- }
- get isBalanceDisplayed() {
- return false;
- }
- get partnerLink() {
- return `/web#model=res.partner&id=${this.state.editModeProps.partner.id}`;
- }
-
- // Methods
-
- async _onPressEnterKey() {
- if (!this.state.query) return;
- if (!this.env.pos.isEveryPartnerLoaded) {
- const result = await this.searchPartner();
- this.showNotification(
- _.str.sprintf(this.env._t('%s customer(s) found for "%s".'),
- result.length,
- this.state.query)
- , 3000);
- if(!result.length) this._clearSearch();
- }
- }
- _clearSearch() {
- this.searchWordInputRef.el.value = '';
- this.state.query = '';
- this.render(true);
- }
- // We declare this event handler as a debounce function in
- // order to lower its trigger rate.
- async updatePartnerList(event) {
- this.state.query = event.target.value;
- if (event.code === 'Enter') {
- this._onPressEnterKey();
- } else {
- this.render(true);
- }
- }
- clickPartner(partner) {
- if (this.state.selectedPartner && this.state.selectedPartner.id === partner.id) {
- this.state.selectedPartner = null;
- } else {
- this.state.selectedPartner = partner;
- }
- this.confirm();
- }
- editPartner(partner) {
- this.state.editModeProps.partner = partner;
- this.activateEditMode();
- }
- createPartner() {
- // initialize the edit screen with default details about country & state
- this.state.editModeProps.partner = {
- country_id: this.env.pos.company.country_id,
- state_id: this.env.pos.company.state_id,
- }
- this.activateEditMode();
- }
- async saveChanges(event) {
- try {
- let partnerId = await this.rpc({
- model: 'res.partner',
- method: 'create_from_ui',
- args: [event.detail.processedChanges],
- });
- await this.env.pos.load_new_partners();
- this.state.selectedPartner = this.env.pos.db.get_partner_by_id(partnerId);
- this.confirm();
- } catch (error) {
- if (isConnectionError(error)) {
- await this.showPopup('OfflineErrorPopup', {
- title: this.env._t('Offline'),
- body: this.env._t('Unable to save changes.'),
- });
- } else {
- throw error;
- }
- }
- }
- async searchPartner() {
- let result = await this.getNewPartners();
- this.env.pos.addPartners(result);
- if (!this.env.pos.isEveryPartnerLoaded) await this.env.pos.updateIsEveryPartnerLoaded();
- this.render(true);
- return result;
- }
- async getNewPartners() {
- let domain = [];
- if(this.state.query) {
- domain = ['|', ["name", "ilike", this.state.query + "%"],
- ["parent_name", "ilike", this.state.query + "%"]];
- }
- const result = await this.env.services.rpc(
- {
- model: 'pos.session',
- method: 'get_pos_ui_res_partner_by_params',
- args: [
- [odoo.pos_session_id],
- {
- domain,
- limit: 10,
- },
- ],
- context: this.env.session.user_context,
- },
- {
- timeout: 3000,
- shadow: true,
- }
- );
- return result;
+ } else {
+ this.props.resolve({ confirmed: false, payload: false });
+ this.trigger("close-temp-screen");
}
}
- PartnerListScreen.template = 'PartnerListScreen';
+ confirm() {
+ this.props.resolve({ confirmed: true, payload: this.state.selectedPartner });
+ this.trigger("close-temp-screen");
+ }
+ activateEditMode() {
+ this.state.detailIsShown = true;
+ this.render(true);
+ }
+ // Getters
- Registries.Component.add(PartnerListScreen);
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
- return PartnerListScreen;
-});
+ get partners() {
+ let res;
+ if (this.state.query && this.state.query.trim() !== "") {
+ res = this.env.pos.db.search_partner(this.state.query.trim());
+ } else {
+ res = this.env.pos.db.get_partners_sorted(1000);
+ }
+ res.sort(function (a, b) {
+ return (a.name || "").localeCompare(b.name || "");
+ });
+ // the selected partner (if any) is displayed at the top of the list
+ if (this.state.selectedPartner) {
+ const indexOfSelectedPartner = res.findIndex(
+ (partner) => partner.id === this.state.selectedPartner.id
+ );
+ if (indexOfSelectedPartner !== -1) {
+ res.splice(indexOfSelectedPartner, 1);
+ res.unshift(this.state.selectedPartner);
+ }
+ }
+ return res;
+ }
+ get isBalanceDisplayed() {
+ return false;
+ }
+ get partnerLink() {
+ return `/web#model=res.partner&id=${this.state.editModeProps.partner.id}`;
+ }
+
+ // Methods
+
+ async _onPressEnterKey() {
+ if (!this.state.query) {
+ return;
+ }
+ if (!this.env.pos.isEveryPartnerLoaded) {
+ const result = await this.searchPartner();
+ this.showNotification(
+ _.str.sprintf(
+ this.env._t('%s customer(s) found for "%s".'),
+ result.length,
+ this.state.query
+ ),
+ 3000
+ );
+ if (!result.length) {
+ this._clearSearch();
+ }
+ }
+ }
+ _clearSearch() {
+ this.searchWordInputRef.el.value = "";
+ this.state.query = "";
+ this.render(true);
+ }
+ // We declare this event handler as a debounce function in
+ // order to lower its trigger rate.
+ async updatePartnerList(event) {
+ this.state.query = event.target.value;
+ if (event.code === "Enter") {
+ this._onPressEnterKey();
+ } else {
+ this.render(true);
+ }
+ }
+ clickPartner(partner) {
+ if (this.state.selectedPartner && this.state.selectedPartner.id === partner.id) {
+ this.state.selectedPartner = null;
+ } else {
+ this.state.selectedPartner = partner;
+ }
+ this.confirm();
+ }
+ editPartner(partner) {
+ this.state.editModeProps.partner = partner;
+ this.activateEditMode();
+ }
+ createPartner() {
+ // initialize the edit screen with default details about country & state
+ this.state.editModeProps.partner = {
+ country_id: this.env.pos.company.country_id,
+ state_id: this.env.pos.company.state_id,
+ };
+ this.activateEditMode();
+ }
+ async saveChanges(event) {
+ try {
+ const partnerId = await this.rpc({
+ model: "res.partner",
+ method: "create_from_ui",
+ args: [event.detail.processedChanges],
+ });
+ await this.env.pos.load_new_partners();
+ this.state.selectedPartner = this.env.pos.db.get_partner_by_id(partnerId);
+ this.confirm();
+ } catch (error) {
+ if (isConnectionError(error)) {
+ await this.showPopup("OfflineErrorPopup", {
+ title: this.env._t("Offline"),
+ body: this.env._t("Unable to save changes."),
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+ async searchPartner() {
+ const result = await this.getNewPartners();
+ this.env.pos.addPartners(result);
+ if (!this.env.pos.isEveryPartnerLoaded) {
+ await this.env.pos.updateIsEveryPartnerLoaded();
+ }
+ this.render(true);
+ return result;
+ }
+ async getNewPartners() {
+ let domain = [];
+ if (this.state.query) {
+ domain = [
+ "|",
+ ["name", "ilike", this.state.query + "%"],
+ ["parent_name", "ilike", this.state.query + "%"],
+ ];
+ }
+ const result = await this.env.services.rpc(
+ {
+ model: "pos.session",
+ method: "get_pos_ui_res_partner_by_params",
+ args: [
+ [odoo.pos_session_id],
+ {
+ domain,
+ limit: 10,
+ },
+ ],
+ context: this.env.session.user_context,
+ },
+ {
+ timeout: 3000,
+ shadow: true,
+ }
+ );
+ return result;
+ }
+}
+PartnerListScreen.template = "PartnerListScreen";
+
+Registries.Component.add(PartnerListScreen);
+
+export default PartnerListScreen;
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js
index b5dc6a7bd54..c5146c7146e 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js
@@ -1,17 +1,15 @@
-odoo.define('point_of_sale.PSNumpadInputButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class PSNumpadInputButton extends PosComponent {
- get _class() {
- return this.props.changeClassTo || 'input-button number-char';
- }
+class PSNumpadInputButton extends PosComponent {
+ get _class() {
+ return this.props.changeClassTo || "input-button number-char";
}
- PSNumpadInputButton.template = 'PSNumpadInputButton';
+}
+PSNumpadInputButton.template = "PSNumpadInputButton";
- Registries.Component.add(PSNumpadInputButton);
+Registries.Component.add(PSNumpadInputButton);
- return PSNumpadInputButton;
-});
+export default PSNumpadInputButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
index 2827ad28086..01e28665cb9 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
@@ -1,456 +1,499 @@
-odoo.define('point_of_sale.PaymentScreen', function (require) {
- 'use strict';
+/** @odoo-module */
- const { parse } = require('web.field_utils');
- 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/core/utils/hooks");
- const Registries = require('point_of_sale.Registries');
- const { isConnectionError } = require('point_of_sale.utils');
- const utils = require('web.utils');
+import { parse } from "web.field_utils";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import { useErrorHandlers } from "@point_of_sale/js/custom_hooks";
+import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
+import { isConnectionError } from "@point_of_sale/js/utils";
+import utils from "web.utils";
- class PaymentScreen extends PosComponent {
- setup() {
- super.setup();
- useListener('delete-payment-line', this.deletePaymentLine);
- useListener('select-payment-line', this.selectPaymentLine);
- useListener('new-payment-line', this.addNewPaymentLine);
- useListener('update-selected-paymentline', this._updateSelectedPaymentline);
- useListener('send-payment-request', this._sendPaymentRequest);
- useListener('send-payment-cancel', this._sendPaymentCancel);
- useListener('send-payment-reverse', this._sendPaymentReverse);
- useListener('send-force-done', this._sendForceDone);
- useListener('validate-order', () => this.validateOrder(false));
- this.payment_methods_from_config = this.env.pos.payment_methods.filter(method => this.env.pos.config.payment_method_ids.includes(method.id));
- NumberBuffer.use(this._getNumberBufferConfig);
- useErrorHandlers();
- this.payment_interface = null;
- this.error = false;
+class PaymentScreen extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("delete-payment-line", this.deletePaymentLine);
+ useListener("select-payment-line", this.selectPaymentLine);
+ useListener("new-payment-line", this.addNewPaymentLine);
+ useListener("update-selected-paymentline", this._updateSelectedPaymentline);
+ useListener("send-payment-request", this._sendPaymentRequest);
+ useListener("send-payment-cancel", this._sendPaymentCancel);
+ useListener("send-payment-reverse", this._sendPaymentReverse);
+ useListener("send-force-done", this._sendForceDone);
+ useListener("validate-order", () => this.validateOrder(false));
+ this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) =>
+ this.env.pos.config.payment_method_ids.includes(method.id)
+ );
+ NumberBuffer.use(this._getNumberBufferConfig);
+ useErrorHandlers();
+ this.payment_interface = null;
+ this.error = false;
+ }
+
+ showMaxValueError() {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Maximum value reached"),
+ body: this.env._t(
+ "The amount cannot be higher than the due amount if you don't have a cash payment method configured."
+ ),
+ });
+ }
+ get _getNumberBufferConfig() {
+ const config = {
+ // The numberBuffer listens to this event to update its state.
+ // Basically means 'update the buffer when this event is triggered'
+ nonKeyboardInputEvent: "input-from-numpad",
+ // When the buffer is updated, trigger this event.
+ // Note that the component listens to it.
+ triggerAtInput: "update-selected-paymentline",
+ };
+ // Check if pos has a cash payment method
+ const hasCashPaymentMethod = this.payment_methods_from_config.some(
+ (method) => method.type === "cash"
+ );
+
+ if (!hasCashPaymentMethod) {
+ config["maxValue"] = this.currentOrder.get_due();
+ config["maxValueReached"] = this.showMaxValueError.bind(this);
}
- showMaxValueError() {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Maximum value reached'),
- body: this.env._t('The amount cannot be higher than the due amount if you don\'t have a cash payment method configured.')
+ return config;
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ get paymentLines() {
+ return this.currentOrder.get_paymentlines();
+ }
+ get selectedPaymentLine() {
+ return this.currentOrder.selected_paymentline;
+ }
+ async selectPartner() {
+ // IMPROVEMENT: This code snippet is repeated multiple times.
+ // Maybe it's better to create a function for it.
+ const currentPartner = this.currentOrder.get_partner();
+ const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", {
+ partner: currentPartner,
+ });
+ if (confirmed) {
+ this.currentOrder.set_partner(newPartner);
+ this.currentOrder.updatePricelist(newPartner);
+ }
+ }
+ addNewPaymentLine({ detail: paymentMethod }) {
+ // original function: click_paymentmethods
+ const result = this.currentOrder.add_paymentline(paymentMethod);
+ if (result) {
+ NumberBuffer.reset();
+ return true;
+ } else {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Error"),
+ body: this.env._t("There is already an electronic payment in progress."),
});
+ return false;
}
- get _getNumberBufferConfig() {
- let config = {
- // The numberBuffer listens to this event to update its state.
- // Basically means 'update the buffer when this event is triggered'
- nonKeyboardInputEvent: 'input-from-numpad',
- // When the buffer is updated, trigger this event.
- // Note that the component listens to it.
- triggerAtInput: 'update-selected-paymentline',
- };
- // Check if pos has a cash payment method
- const hasCashPaymentMethod = this.payment_methods_from_config.some(
- (method) => method.type === 'cash'
- );
+ }
+ _updateSelectedPaymentline() {
+ if (this.paymentLines.every((line) => line.paid)) {
+ this.currentOrder.add_paymentline(this.payment_methods_from_config[0]);
+ }
+ if (!this.selectedPaymentLine) {
+ return;
+ } // do nothing if no selected payment line
+ // disable changing amount on paymentlines with running or done payments on a payment terminal
+ const payment_terminal = this.selectedPaymentLine.payment_method.payment_terminal;
+ if (
+ payment_terminal &&
+ !["pending", "retry"].includes(this.selectedPaymentLine.get_payment_status())
+ ) {
+ return;
+ }
+ if (NumberBuffer.get() === null) {
+ this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } });
+ } else {
+ this.selectedPaymentLine.set_amount(NumberBuffer.getFloat());
+ }
+ }
+ toggleIsToInvoice() {
+ // click_invoice
+ this.currentOrder.set_to_invoice(!this.currentOrder.is_to_invoice());
+ this.render(true);
+ }
+ openCashbox() {
+ this.env.proxy.printer.open_cashbox();
+ }
+ async addTip() {
+ // click_tip
+ const tip = this.currentOrder.get_tip();
+ const change = this.currentOrder.get_change();
+ const value = tip === 0 && change > 0 ? change : tip;
- if (!hasCashPaymentMethod) {
- config['maxValue'] = this.currentOrder.get_due();
- config['maxValueReached'] = this.showMaxValueError.bind(this);
- }
+ const { confirmed, payload } = await this.showPopup("NumberPopup", {
+ title: tip ? this.env._t("Change Tip") : this.env._t("Add Tip"),
+ startingValue: value,
+ isInputSelected: true,
+ });
- return config;
+ if (confirmed) {
+ this.currentOrder.set_tip(parse.float(payload));
}
- get currentOrder() {
- return this.env.pos.get_order();
- }
- get paymentLines() {
- return this.currentOrder.get_paymentlines();
- }
- get selectedPaymentLine() {
- return this.currentOrder.selected_paymentline;
- }
- async selectPartner() {
- // IMPROVEMENT: This code snippet is repeated multiple times.
- // Maybe it's better to create a function for it.
- const currentPartner = this.currentOrder.get_partner();
- const { confirmed, payload: newPartner } = await this.showTempScreen(
- 'PartnerListScreen',
- { partner: currentPartner }
- );
- if (confirmed) {
- this.currentOrder.set_partner(newPartner);
- this.currentOrder.updatePricelist(newPartner);
- }
- }
- addNewPaymentLine({ detail: paymentMethod }) {
- // original function: click_paymentmethods
- let result = this.currentOrder.add_paymentline(paymentMethod);
- if (result){
- NumberBuffer.reset();
- return true;
- }
- else{
- this.showPopup('ErrorPopup', {
- title: this.env._t('Error'),
- body: this.env._t('There is already an electronic payment in progress.'),
- });
- return false;
- }
- }
- _updateSelectedPaymentline() {
- if (this.paymentLines.every((line) => line.paid)) {
- this.currentOrder.add_paymentline(this.payment_methods_from_config[0]);
- }
- if (!this.selectedPaymentLine) return; // do nothing if no selected payment line
- // disable changing amount on paymentlines with running or done payments on a payment terminal
- const payment_terminal = this.selectedPaymentLine.payment_method.payment_terminal;
- if (
- payment_terminal &&
- !['pending', 'retry'].includes(this.selectedPaymentLine.get_payment_status())
- ) {
- return;
- }
- if (NumberBuffer.get() === null) {
- this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } });
- } else {
- this.selectedPaymentLine.set_amount(NumberBuffer.getFloat());
- }
- }
- toggleIsToInvoice() {
- // click_invoice
- this.currentOrder.set_to_invoice(!this.currentOrder.is_to_invoice());
- this.render(true);
- }
- openCashbox() {
- this.env.proxy.printer.open_cashbox();
- }
- async addTip() {
- // click_tip
- const tip = this.currentOrder.get_tip();
- const change = this.currentOrder.get_change();
- let value = tip === 0 && change > 0 ? change : tip;
+ }
+ toggleIsToShip() {
+ // click_ship
+ this.currentOrder.set_to_ship(!this.currentOrder.is_to_ship());
+ this.render(true);
+ }
+ deletePaymentLine(event) {
+ var self = this;
+ const { cid } = event.detail;
+ const line = this.paymentLines.find((line) => line.cid === cid);
- const { confirmed, payload } = await this.showPopup('NumberPopup', {
- title: tip ? this.env._t('Change Tip') : this.env._t('Add Tip'),
- startingValue: value,
- isInputSelected: true,
- });
-
- if (confirmed) {
- this.currentOrder.set_tip(parse.float(payload));
- }
- }
- toggleIsToShip() {
- // click_ship
- this.currentOrder.set_to_ship(!this.currentOrder.is_to_ship());
- this.render(true);
- }
- deletePaymentLine(event) {
- var self = this;
- const { cid } = event.detail;
- const line = this.paymentLines.find((line) => line.cid === cid);
-
- // If a paymentline with a payment terminal linked to
- // it is removed, the terminal should get a cancel
- // request.
- if (['waiting', 'waitingCard', 'timeout'].includes(line.get_payment_status())) {
- line.set_payment_status('waitingCancel');
- line.payment_method.payment_terminal.send_payment_cancel(this.currentOrder, cid).then(function() {
+ // If a paymentline with a payment terminal linked to
+ // it is removed, the terminal should get a cancel
+ // request.
+ if (["waiting", "waitingCard", "timeout"].includes(line.get_payment_status())) {
+ line.set_payment_status("waitingCancel");
+ line.payment_method.payment_terminal
+ .send_payment_cancel(this.currentOrder, cid)
+ .then(function () {
self.currentOrder.remove_paymentline(line);
NumberBuffer.reset();
self.render(true);
- })
- }
- else if (line.get_payment_status() !== 'waitingCancel') {
- this.currentOrder.remove_paymentline(line);
- NumberBuffer.reset();
- this.render(true);
- }
- }
- selectPaymentLine(event) {
- const { cid } = event.detail;
- const line = this.paymentLines.find((line) => line.cid === cid);
- this.currentOrder.select_paymentline(line);
+ });
+ } else if (line.get_payment_status() !== "waitingCancel") {
+ this.currentOrder.remove_paymentline(line);
NumberBuffer.reset();
this.render(true);
}
- async validateOrder(isForceValidate) {
- if(this.env.pos.config.cash_rounding) {
- if(!this.env.pos.get_order().check_paymentlines_rounding()) {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Rounding error in payment lines'),
- body: this.env._t("The amount of your payment lines must be rounded to validate the transaction."),
- });
- return;
- }
- }
- if (await this._isOrderValid(isForceValidate)) {
- // remove pending payments before finalizing the validation
- for (let line of this.paymentLines) {
- if (!line.is_done()) this.currentOrder.remove_paymentline(line);
- }
- await this._finalizeValidation();
- }
- }
- async _finalizeValidation() {
- if ((this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) && this.env.pos.config.iface_cashdrawer) {
- this.env.proxy.printer.open_cashbox();
- }
-
- this.currentOrder.initialize_validation_date();
- this.currentOrder.finalized = true;
-
- let syncOrderResult, hasError;
-
- try {
- // 1. Save order to server.
- syncOrderResult = await this.env.pos.push_single_order(this.currentOrder);
-
- // 2. Invoice.
- if (this.currentOrder.is_to_invoice()) {
- if (syncOrderResult.length) {
- await this.env.legacyActionManager.do_action('account.account_invoices', {
- additional_context: {
- active_ids: [syncOrderResult[0].account_move],
- },
- });
- } else {
- throw { code: 401, message: 'Backend Invoice', data: { order: this.currentOrder } };
- }
- }
-
- // 3. Post process.
- if (syncOrderResult.length && this.currentOrder.wait_for_push_order()) {
- const postPushResult = await this._postPushOrderResolve(
- this.currentOrder,
- syncOrderResult.map((res) => res.id)
- );
- if (!postPushResult) {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Error: no internet connection.'),
- body: this.env._t('Some, if not all, post-processing after syncing order failed.'),
- });
- }
- }
- } catch (error) {
- if (error.code == 700 || error.code == 701)
- this.error = true;
-
- if ('code' in error) {
- // We started putting `code` in the rejected object for invoicing error.
- // We can continue with that convention such that when the error has `code`,
- // then it is an error when invoicing. Besides, _handlePushOrderError was
- // introduce to handle invoicing error logic.
- await this._handlePushOrderError(error);
- } else {
- // We don't block for connection error. But we rethrow for any other errors.
- if (isConnectionError(error)) {
- this.showPopup('OfflineErrorPopup', {
- title: this.env._t('Connection Error'),
- body: this.env._t('Order is not synced. Check your internet connection'),
- });
- } else {
- throw error;
- }
- }
- } finally {
- // Always show the next screen regardless of error since pos has to
- // continue working even offline.
- this.showScreen(this.nextScreen);
- // Remove the order from the local storage so that when we refresh the page, the order
- // won't be there
- this.env.pos.db.remove_unpaid_order(this.currentOrder);
-
- // Ask the user to sync the remaining unsynced orders.
- if (!hasError && syncOrderResult && this.env.pos.db.get_orders().length) {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Remaining unsynced orders'),
- body: this.env._t(
- 'There are unsynced orders. Do you want to sync these orders?'
- ),
- });
- if (confirmed) {
- // NOTE: Not yet sure if this should be awaited or not.
- // If awaited, some operations like changing screen
- // might not work.
- this.env.pos.push_orders();
- }
- }
- }
- }
- get nextScreen() {
- return !this.error? 'ReceiptScreen' : 'ProductScreen';
- }
- async _isOrderValid(isForceValidate) {
- if (this.currentOrder.get_orderlines().length === 0 && this.currentOrder.is_to_invoice()) {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Empty Order'),
+ }
+ selectPaymentLine(event) {
+ const { cid } = event.detail;
+ const line = this.paymentLines.find((line) => line.cid === cid);
+ this.currentOrder.select_paymentline(line);
+ NumberBuffer.reset();
+ this.render(true);
+ }
+ async validateOrder(isForceValidate) {
+ if (this.env.pos.config.cash_rounding) {
+ if (!this.env.pos.get_order().check_paymentlines_rounding()) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Rounding error in payment lines"),
body: this.env._t(
- 'There must be at least one product in your order before it can be validated and invoiced.'
+ "The amount of your payment lines must be rounded to validate the transaction."
),
});
- return false;
- }
-
- const splitPayments = this.paymentLines.filter(payment => payment.payment_method.split_transactions)
- if (splitPayments.length && !this.currentOrder.get_partner()) {
- const paymentMethod = splitPayments[0].payment_method
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Customer Required'),
- body: _.str.sprintf(this.env._t('Customer is required for %s payment method.'), paymentMethod.name),
- });
- if (confirmed) {
- this.selectPartner();
- }
- return false;
- }
-
- if ((this.currentOrder.is_to_invoice() || this.currentOrder.is_to_ship()) && !this.currentOrder.get_partner()) {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Please select the Customer'),
- body: this.env._t(
- 'You need to select the customer before you can invoice or ship an order.'
- ),
- });
- if (confirmed) {
- this.selectPartner();
- }
- return false;
- }
-
- let partner = this.currentOrder.get_partner()
- if (this.currentOrder.is_to_ship() && !(partner.name && partner.street && partner.city && partner.country_id)) {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Incorrect address for shipping'),
- body: this.env._t('The selected customer needs an address.'),
- });
- return false;
- }
-
- if (this.currentOrder.get_total_with_tax() != 0 && this.currentOrder.get_paymentlines().length === 0) {
- this.showNotification(this.env._t('Select a payment method to validate the order.'));
- return false;
- }
-
- if (!this.currentOrder.is_paid() || this.invoicing) {
- return false;
- }
-
- if (this.currentOrder.has_not_valid_rounding()) {
- var line = this.currentOrder.has_not_valid_rounding();
- this.showPopup('ErrorPopup', {
- title: this.env._t('Incorrect rounding'),
- body: this.env._t(
- 'You have to round your payments lines.' + line.amount + ' is not rounded.'
- ),
- });
- return false;
- }
-
- // The exact amount must be paid if there is no cash payment method defined.
- if (
- Math.abs(
- this.currentOrder.get_total_with_tax() - this.currentOrder.get_total_paid() + this.currentOrder.get_rounding_applied()
- ) > 0.00001
- ) {
- var cash = false;
- for (var i = 0; i < this.env.pos.payment_methods.length; i++) {
- cash = cash || this.env.pos.payment_methods[i].is_cash_count;
- }
- if (!cash) {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Cannot return change without a cash payment method'),
- body: this.env._t(
- 'There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'
- ),
- });
- return false;
- }
- }
-
- // if the change is too large, it's probably an input error, make the user confirm.
- if (
- !isForceValidate &&
- this.currentOrder.get_total_with_tax() > 0 &&
- this.currentOrder.get_total_with_tax() * 1000 < this.currentOrder.get_total_paid()
- ) {
- this.showPopup('ConfirmPopup', {
- title: this.env._t('Please Confirm Large Amount'),
- body:
- this.env._t('Are you sure that the customer wants to pay') +
- ' ' +
- this.env.pos.format_currency(this.currentOrder.get_total_paid()) +
- ' ' +
- this.env._t('for an order of') +
- ' ' +
- this.env.pos.format_currency(this.currentOrder.get_total_with_tax()) +
- ' ' +
- this.env._t('? Clicking "Confirm" will validate the payment.'),
- }).then(({ confirmed }) => {
- if (confirmed) this.validateOrder(true);
- });
- return false;
- }
-
- if (!this.currentOrder._isValidEmptyOrder()) return false;
-
- return true;
- }
- async _postPushOrderResolve(order, order_server_ids) {
- return true;
- }
- async _sendPaymentRequest({ detail: line }) {
- // Other payment lines can not be reversed anymore
- this.paymentLines.forEach(function (line) {
- line.can_be_reversed = false;
- });
-
- const payment_terminal = line.payment_method.payment_terminal;
- line.set_payment_status('waiting');
-
- const isPaymentSuccessful = await payment_terminal.send_payment_request(line.cid);
- if (isPaymentSuccessful) {
- line.set_payment_status('done');
- line.can_be_reversed = payment_terminal.supports_reversals;
- // Automatically validate the order when after an electronic payment,
- // the current order is fully paid and due is zero.
- if (
- this.currentOrder.is_paid() &&
- utils.float_is_zero(this.currentOrder.get_due(), this.env.pos.currency.decimal_places) &&
- this.env.pos.config.auto_validate_terminal_payment
- ) {
- this.trigger('validate-order');
- }
- } else {
- line.set_payment_status('retry');
+ return;
}
}
- async _sendPaymentCancel({ detail: line }) {
- const payment_terminal = line.payment_method.payment_terminal;
- line.set_payment_status('waitingCancel');
- const isCancelSuccessful = await payment_terminal.send_payment_cancel(this.currentOrder, line.cid);
- if (isCancelSuccessful) {
- line.set_payment_status('retry');
- } else {
- line.set_payment_status('waitingCard');
+ if (await this._isOrderValid(isForceValidate)) {
+ // remove pending payments before finalizing the validation
+ for (const line of this.paymentLines) {
+ if (!line.is_done()) {
+ this.currentOrder.remove_paymentline(line);
+ }
}
- }
- async _sendPaymentReverse({ detail: line }) {
- const payment_terminal = line.payment_method.payment_terminal;
- line.set_payment_status('reversing');
-
- const isReversalSuccessful = await payment_terminal.send_payment_reversal(line.cid);
- if (isReversalSuccessful) {
- line.set_amount(0);
- line.set_payment_status('reversed');
- } else {
- line.can_be_reversed = false;
- line.set_payment_status('done');
- }
- }
- async _sendForceDone({ detail: line }) {
- line.set_payment_status('done');
+ await this._finalizeValidation();
}
}
- PaymentScreen.template = 'PaymentScreen';
+ async _finalizeValidation() {
+ if (
+ (this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) &&
+ this.env.pos.config.iface_cashdrawer
+ ) {
+ this.env.proxy.printer.open_cashbox();
+ }
- Registries.Component.add(PaymentScreen);
+ this.currentOrder.initialize_validation_date();
+ this.currentOrder.finalized = true;
- return PaymentScreen;
-});
+ let syncOrderResult, hasError;
+
+ try {
+ // 1. Save order to server.
+ syncOrderResult = await this.env.pos.push_single_order(this.currentOrder);
+
+ // 2. Invoice.
+ if (this.currentOrder.is_to_invoice()) {
+ if (syncOrderResult.length) {
+ await this.env.legacyActionManager.do_action("account.account_invoices", {
+ additional_context: {
+ active_ids: [syncOrderResult[0].account_move],
+ },
+ });
+ } else {
+ throw {
+ code: 401,
+ message: "Backend Invoice",
+ data: { order: this.currentOrder },
+ };
+ }
+ }
+
+ // 3. Post process.
+ if (syncOrderResult.length && this.currentOrder.wait_for_push_order()) {
+ const postPushResult = await this._postPushOrderResolve(
+ this.currentOrder,
+ syncOrderResult.map((res) => res.id)
+ );
+ if (!postPushResult) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Error: no internet connection."),
+ body: this.env._t(
+ "Some, if not all, post-processing after syncing order failed."
+ ),
+ });
+ }
+ }
+ } catch (error) {
+ if (error.code == 700 || error.code == 701) {
+ this.error = true;
+ }
+
+ if ("code" in error) {
+ // We started putting `code` in the rejected object for invoicing error.
+ // We can continue with that convention such that when the error has `code`,
+ // then it is an error when invoicing. Besides, _handlePushOrderError was
+ // introduce to handle invoicing error logic.
+ await this._handlePushOrderError(error);
+ } else {
+ // We don't block for connection error. But we rethrow for any other errors.
+ if (isConnectionError(error)) {
+ this.showPopup("OfflineErrorPopup", {
+ title: this.env._t("Connection Error"),
+ body: this.env._t("Order is not synced. Check your internet connection"),
+ });
+ } else {
+ throw error;
+ }
+ }
+ } finally {
+ // Always show the next screen regardless of error since pos has to
+ // continue working even offline.
+ this.showScreen(this.nextScreen);
+ // Remove the order from the local storage so that when we refresh the page, the order
+ // won't be there
+ this.env.pos.db.remove_unpaid_order(this.currentOrder);
+
+ // Ask the user to sync the remaining unsynced orders.
+ if (!hasError && syncOrderResult && this.env.pos.db.get_orders().length) {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Remaining unsynced orders"),
+ body: this.env._t(
+ "There are unsynced orders. Do you want to sync these orders?"
+ ),
+ });
+ if (confirmed) {
+ // NOTE: Not yet sure if this should be awaited or not.
+ // If awaited, some operations like changing screen
+ // might not work.
+ this.env.pos.push_orders();
+ }
+ }
+ }
+ }
+ get nextScreen() {
+ return !this.error ? "ReceiptScreen" : "ProductScreen";
+ }
+ async _isOrderValid(isForceValidate) {
+ if (this.currentOrder.get_orderlines().length === 0 && this.currentOrder.is_to_invoice()) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Empty Order"),
+ body: this.env._t(
+ "There must be at least one product in your order before it can be validated and invoiced."
+ ),
+ });
+ return false;
+ }
+
+ const splitPayments = this.paymentLines.filter(
+ (payment) => payment.payment_method.split_transactions
+ );
+ if (splitPayments.length && !this.currentOrder.get_partner()) {
+ const paymentMethod = splitPayments[0].payment_method;
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Customer Required"),
+ body: _.str.sprintf(
+ this.env._t("Customer is required for %s payment method."),
+ paymentMethod.name
+ ),
+ });
+ if (confirmed) {
+ this.selectPartner();
+ }
+ return false;
+ }
+
+ if (
+ (this.currentOrder.is_to_invoice() || this.currentOrder.is_to_ship()) &&
+ !this.currentOrder.get_partner()
+ ) {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Please select the Customer"),
+ body: this.env._t(
+ "You need to select the customer before you can invoice or ship an order."
+ ),
+ });
+ if (confirmed) {
+ this.selectPartner();
+ }
+ return false;
+ }
+
+ const partner = this.currentOrder.get_partner();
+ if (
+ this.currentOrder.is_to_ship() &&
+ !(partner.name && partner.street && partner.city && partner.country_id)
+ ) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Incorrect address for shipping"),
+ body: this.env._t("The selected customer needs an address."),
+ });
+ return false;
+ }
+
+ if (
+ this.currentOrder.get_total_with_tax() != 0 &&
+ this.currentOrder.get_paymentlines().length === 0
+ ) {
+ this.showNotification(this.env._t("Select a payment method to validate the order."));
+ return false;
+ }
+
+ if (!this.currentOrder.is_paid() || this.invoicing) {
+ return false;
+ }
+
+ if (this.currentOrder.has_not_valid_rounding()) {
+ var line = this.currentOrder.has_not_valid_rounding();
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Incorrect rounding"),
+ body: this.env._t(
+ "You have to round your payments lines." + line.amount + " is not rounded."
+ ),
+ });
+ return false;
+ }
+
+ // The exact amount must be paid if there is no cash payment method defined.
+ if (
+ Math.abs(
+ this.currentOrder.get_total_with_tax() -
+ this.currentOrder.get_total_paid() +
+ this.currentOrder.get_rounding_applied()
+ ) > 0.00001
+ ) {
+ var cash = false;
+ for (var i = 0; i < this.env.pos.payment_methods.length; i++) {
+ cash = cash || this.env.pos.payment_methods[i].is_cash_count;
+ }
+ if (!cash) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Cannot return change without a cash payment method"),
+ body: this.env._t(
+ "There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration"
+ ),
+ });
+ return false;
+ }
+ }
+
+ // if the change is too large, it's probably an input error, make the user confirm.
+ if (
+ !isForceValidate &&
+ this.currentOrder.get_total_with_tax() > 0 &&
+ this.currentOrder.get_total_with_tax() * 1000 < this.currentOrder.get_total_paid()
+ ) {
+ this.showPopup("ConfirmPopup", {
+ title: this.env._t("Please Confirm Large Amount"),
+ body:
+ this.env._t("Are you sure that the customer wants to pay") +
+ " " +
+ this.env.pos.format_currency(this.currentOrder.get_total_paid()) +
+ " " +
+ this.env._t("for an order of") +
+ " " +
+ this.env.pos.format_currency(this.currentOrder.get_total_with_tax()) +
+ " " +
+ this.env._t('? Clicking "Confirm" will validate the payment.'),
+ }).then(({ confirmed }) => {
+ if (confirmed) {
+ this.validateOrder(true);
+ }
+ });
+ return false;
+ }
+
+ if (!this.currentOrder._isValidEmptyOrder()) {
+ return false;
+ }
+
+ return true;
+ }
+ async _postPushOrderResolve(order, order_server_ids) {
+ return true;
+ }
+ async _sendPaymentRequest({ detail: line }) {
+ // Other payment lines can not be reversed anymore
+ this.paymentLines.forEach(function (line) {
+ line.can_be_reversed = false;
+ });
+
+ const payment_terminal = line.payment_method.payment_terminal;
+ line.set_payment_status("waiting");
+
+ const isPaymentSuccessful = await payment_terminal.send_payment_request(line.cid);
+ if (isPaymentSuccessful) {
+ line.set_payment_status("done");
+ line.can_be_reversed = payment_terminal.supports_reversals;
+ // Automatically validate the order when after an electronic payment,
+ // the current order is fully paid and due is zero.
+ if (
+ this.currentOrder.is_paid() &&
+ utils.float_is_zero(
+ this.currentOrder.get_due(),
+ this.env.pos.currency.decimal_places
+ ) &&
+ this.env.pos.config.auto_validate_terminal_payment
+ ) {
+ this.trigger("validate-order");
+ }
+ } else {
+ line.set_payment_status("retry");
+ }
+ }
+ async _sendPaymentCancel({ detail: line }) {
+ const payment_terminal = line.payment_method.payment_terminal;
+ line.set_payment_status("waitingCancel");
+ const isCancelSuccessful = await payment_terminal.send_payment_cancel(
+ this.currentOrder,
+ line.cid
+ );
+ if (isCancelSuccessful) {
+ line.set_payment_status("retry");
+ } else {
+ line.set_payment_status("waitingCard");
+ }
+ }
+ async _sendPaymentReverse({ detail: line }) {
+ const payment_terminal = line.payment_method.payment_terminal;
+ line.set_payment_status("reversing");
+
+ const isReversalSuccessful = await payment_terminal.send_payment_reversal(line.cid);
+ if (isReversalSuccessful) {
+ line.set_amount(0);
+ line.set_payment_status("reversed");
+ } else {
+ line.can_be_reversed = false;
+ line.set_payment_status("done");
+ }
+ }
+ async _sendForceDone({ detail: line }) {
+ line.set_payment_status("done");
+ }
+}
+PaymentScreen.template = "PaymentScreen";
+
+Registries.Component.add(PaymentScreen);
+
+export default PaymentScreen;
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js
index 4ec3536734f..8db09d3f16f 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js
@@ -1,18 +1,16 @@
-odoo.define('point_of_sale.PaymentScreenNumpad', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class PaymentScreenNumpad extends PosComponent {
- setup() {
- super.setup();
- this.decimalPoint = this.env._t.database.parameters.decimal_point;
- }
+class PaymentScreenNumpad extends PosComponent {
+ setup() {
+ super.setup();
+ this.decimalPoint = this.env._t.database.parameters.decimal_point;
}
- PaymentScreenNumpad.template = 'PaymentScreenNumpad';
+}
+PaymentScreenNumpad.template = "PaymentScreenNumpad";
- Registries.Component.add(PaymentScreenNumpad);
+Registries.Component.add(PaymentScreenNumpad);
- return PaymentScreenNumpad;
-});
+export default PaymentScreenNumpad;
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js
index 8f2311468f3..7a0b4185284 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js
@@ -1,23 +1,21 @@
-odoo.define('point_of_sale.PaymentScreenPaymentLines', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class PaymentScreenPaymentLines extends PosComponent {
- formatLineAmount(paymentline) {
- return this.env.pos.format_currency_no_symbol(paymentline.get_amount());
- }
- selectedLineClass(line) {
- return { 'payment-terminal': line.get_payment_status() };
- }
- unselectedLineClass(line) {
- return {};
- }
+class PaymentScreenPaymentLines extends PosComponent {
+ formatLineAmount(paymentline) {
+ return this.env.pos.format_currency_no_symbol(paymentline.get_amount());
}
- PaymentScreenPaymentLines.template = 'PaymentScreenPaymentLines';
+ selectedLineClass(line) {
+ return { "payment-terminal": line.get_payment_status() };
+ }
+ unselectedLineClass(line) {
+ return {};
+ }
+}
+PaymentScreenPaymentLines.template = "PaymentScreenPaymentLines";
- Registries.Component.add(PaymentScreenPaymentLines);
+Registries.Component.add(PaymentScreenPaymentLines);
- return PaymentScreenPaymentLines;
-});
+export default PaymentScreenPaymentLines;
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js
index afcb08ed303..2cdc39e7081 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js
@@ -1,27 +1,25 @@
-odoo.define('point_of_sale.PaymentScreenStatus', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class PaymentScreenStatus extends PosComponent {
- get changeText() {
- return this.env.pos.format_currency(this.props.order.get_change());
- }
- get totalDueText() {
- return this.env.pos.format_currency(
- this.props.order.get_total_with_tax() + this.props.order.get_rounding_applied()
- );
- }
- get remainingText() {
- return this.env.pos.format_currency(
- this.props.order.get_due() > 0 ? this.props.order.get_due() : 0
- );
- }
+class PaymentScreenStatus extends PosComponent {
+ get changeText() {
+ return this.env.pos.format_currency(this.props.order.get_change());
}
- PaymentScreenStatus.template = 'PaymentScreenStatus';
+ get totalDueText() {
+ return this.env.pos.format_currency(
+ this.props.order.get_total_with_tax() + this.props.order.get_rounding_applied()
+ );
+ }
+ get remainingText() {
+ return this.env.pos.format_currency(
+ this.props.order.get_due() > 0 ? this.props.order.get_due() : 0
+ );
+ }
+}
+PaymentScreenStatus.template = "PaymentScreenStatus";
- Registries.Component.add(PaymentScreenStatus);
+Registries.Component.add(PaymentScreenStatus);
- return PaymentScreenStatus;
-});
+export default PaymentScreenStatus;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js
index fec19079d7c..b7b8ece117e 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js
@@ -1,25 +1,23 @@
-odoo.define('point_of_sale.ActionpadWidget', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- /**
- * @props partner
- * @emits click-partner
- * @emits click-pay
- */
- class ActionpadWidget extends PosComponent {
- get isLongName() {
- return this.props.partner && this.props.partner.name.length > 10;
- }
- }
- ActionpadWidget.template = 'ActionpadWidget';
- ActionpadWidget.defaultProps = {
- isActionButtonHighlighted: false,
+/**
+ * @props partner
+ * @emits click-partner
+ * @emits click-pay
+ */
+class ActionpadWidget extends PosComponent {
+ get isLongName() {
+ return this.props.partner && this.props.partner.name.length > 10;
}
+}
+ActionpadWidget.template = "ActionpadWidget";
+ActionpadWidget.defaultProps = {
+ isActionButtonHighlighted: false,
+};
- Registries.Component.add(ActionpadWidget);
+Registries.Component.add(ActionpadWidget);
- return ActionpadWidget;
-});
+export default ActionpadWidget;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js
index 9aa6914e087..af034bf1d1a 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js
@@ -1,18 +1,16 @@
-odoo.define('point_of_sale.CategoryButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class CategoryButton extends PosComponent {
- get imageUrl() {
- const category = this.props.category
- return `/web/image?model=pos.category&field=image_128&id=${category.id}&unique=${category.write_date}`;
- }
+class CategoryButton extends PosComponent {
+ get imageUrl() {
+ const category = this.props.category;
+ return `/web/image?model=pos.category&field=image_128&id=${category.id}&unique=${category.write_date}`;
}
- CategoryButton.template = 'CategoryButton';
+}
+CategoryButton.template = "CategoryButton";
- Registries.Component.add(CategoryButton);
+Registries.Component.add(CategoryButton);
- return CategoryButton;
-});
+export default CategoryButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js
index 36f3fefd9f0..2445f3e3d67 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js
@@ -1,37 +1,37 @@
-odoo.define('point_of_sale.OrderlineCustomerNoteButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const ProductScreen = require('point_of_sale.ProductScreen');
- const { useListener } = require("@web/core/utils/hooks");
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
- class OrderlineCustomerNoteButton extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this.onClick);
+class OrderlineCustomerNoteButton extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this.onClick);
+ }
+ async onClick() {
+ const selectedOrderline = this.env.pos.get_order().get_selected_orderline();
+ if (!selectedOrderline) {
+ return;
}
- async onClick() {
- const selectedOrderline = this.env.pos.get_order().get_selected_orderline();
- if (!selectedOrderline) return;
- const { confirmed, payload: inputNote } = await this.showPopup('TextAreaPopup', {
- startingValue: selectedOrderline.get_customer_note(),
- title: this.env._t('Add Customer Note'),
- });
+ const { confirmed, payload: inputNote } = await this.showPopup("TextAreaPopup", {
+ startingValue: selectedOrderline.get_customer_note(),
+ title: this.env._t("Add Customer Note"),
+ });
- if (confirmed) {
- selectedOrderline.set_customer_note(inputNote);
- }
+ if (confirmed) {
+ selectedOrderline.set_customer_note(inputNote);
}
}
- OrderlineCustomerNoteButton.template = 'OrderlineCustomerNoteButton';
+}
+OrderlineCustomerNoteButton.template = "OrderlineCustomerNoteButton";
- ProductScreen.addControlButton({
- component: OrderlineCustomerNoteButton,
- });
-
- Registries.Component.add(OrderlineCustomerNoteButton);
-
- return OrderlineCustomerNoteButton;
+ProductScreen.addControlButton({
+ component: OrderlineCustomerNoteButton,
});
+
+Registries.Component.add(OrderlineCustomerNoteButton);
+
+export default OrderlineCustomerNoteButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js
index 635d5660880..38cafb7ada2 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js
@@ -1,50 +1,50 @@
-odoo.define('point_of_sale.ProductInfoButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const ProductScreen = require('point_of_sale.ProductScreen');
- const { useListener } = require("@web/core/utils/hooks");
- const Registries = require('point_of_sale.Registries');
- const { ConnectionLostError, ConnectionAbortedError } = require('@web/core/network/rpc_service')
- const { identifyError } = require('point_of_sale.utils');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
+import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service";
+import { identifyError } from "@point_of_sale/js/utils";
- class ProductInfoButton extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this.onClick);
- }
- async onClick() {
- const orderline = this.env.pos.get_order().get_selected_orderline();
- if (orderline) {
- const product = orderline.get_product();
- const quantity = orderline.get_quantity();
- try {
- const info = await this.env.pos.getProductInfo(product, quantity);
- this.showPopup('ProductInfoPopup', { info: info , product: product });
- } catch (e) {
- if (identifyError(e) instanceof ConnectionLostError||ConnectionAbortedError) {
- this.showPopup('OfflineErrorPopup', {
- title: this.env._t('Network Error'),
- body: this.env._t('Cannot access product information screen if offline.'),
- });
- } else {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Unknown error'),
- body: this.env._t('An unknown error prevents us from loading product information.'),
- });
- }
+class ProductInfoButton extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this.onClick);
+ }
+ async onClick() {
+ const orderline = this.env.pos.get_order().get_selected_orderline();
+ if (orderline) {
+ const product = orderline.get_product();
+ const quantity = orderline.get_quantity();
+ try {
+ const info = await this.env.pos.getProductInfo(product, quantity);
+ this.showPopup("ProductInfoPopup", { info: info, product: product });
+ } catch (e) {
+ if (identifyError(e) instanceof ConnectionLostError || ConnectionAbortedError) {
+ this.showPopup("OfflineErrorPopup", {
+ title: this.env._t("Network Error"),
+ body: this.env._t("Cannot access product information screen if offline."),
+ });
+ } else {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Unknown error"),
+ body: this.env._t(
+ "An unknown error prevents us from loading product information."
+ ),
+ });
}
}
}
}
- ProductInfoButton.template = 'ProductInfoButton';
+}
+ProductInfoButton.template = "ProductInfoButton";
- ProductScreen.addControlButton({
- component: ProductInfoButton,
- position: ['before', 'SetFiscalPositionButton'],
- });
-
- Registries.Component.add(ProductInfoButton);
-
- return ProductInfoButton;
+ProductScreen.addControlButton({
+ component: ProductInfoButton,
+ position: ["before", "SetFiscalPositionButton"],
});
+
+Registries.Component.add(ProductInfoButton);
+
+export default ProductInfoButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js
index 0c0799fdffa..b21861ff640 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js
@@ -1,35 +1,33 @@
-odoo.define('point_of_sale.RefundButton', function (require) {
- 'use strict';
+/** @odoo-module */
- 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/core/utils/hooks");
+import PosComponent from "@point_of_sale/js/PosComponent";
+import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
+import Registries from "@point_of_sale/js/Registries";
+import { useListener } from "@web/core/utils/hooks";
- class RefundButton extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this._onClick);
- }
- _onClick() {
- const partner = this.env.pos.get_order().get_partner();
- const searchDetails = partner ? { fieldName: 'PARTNER', searchTerm: partner.name } : {};
- this.showScreen('TicketScreen', {
- ui: { filter: 'SYNCED', searchDetails },
- destinationOrder: this.env.pos.get_order(),
- });
- }
+class RefundButton extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this._onClick);
}
- RefundButton.template = 'point_of_sale.RefundButton';
+ _onClick() {
+ const partner = this.env.pos.get_order().get_partner();
+ const searchDetails = partner ? { fieldName: "PARTNER", searchTerm: partner.name } : {};
+ this.showScreen("TicketScreen", {
+ ui: { filter: "SYNCED", searchDetails },
+ destinationOrder: this.env.pos.get_order(),
+ });
+ }
+}
+RefundButton.template = "point_of_sale.RefundButton";
- ProductScreen.addControlButton({
- component: RefundButton,
- condition: function () {
- return true;
- },
- });
-
- Registries.Component.add(RefundButton);
-
- return RefundButton;
+ProductScreen.addControlButton({
+ component: RefundButton,
+ condition: function () {
+ return true;
+ },
});
+
+Registries.Component.add(RefundButton);
+
+export default RefundButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js
index 226ff0971cc..0a0a55f2aec 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js
@@ -1,71 +1,69 @@
-odoo.define('point_of_sale.SetFiscalPositionButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const ProductScreen = require('point_of_sale.ProductScreen');
- const { useListener } = require("@web/core/utils/hooks");
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
- class SetFiscalPositionButton extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this.onClick);
+class SetFiscalPositionButton extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this.onClick);
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ get currentFiscalPositionName() {
+ return this.currentOrder && this.currentOrder.fiscal_position
+ ? this.currentOrder.fiscal_position.display_name
+ : this.env._t("Tax");
+ }
+ async onClick() {
+ const currentFiscalPosition = this.currentOrder.fiscal_position;
+ const fiscalPosList = [
+ {
+ id: -1,
+ label: this.env._t("None"),
+ isSelected: !currentFiscalPosition,
+ },
+ ];
+ for (const fiscalPos of this.env.pos.fiscal_positions) {
+ fiscalPosList.push({
+ id: fiscalPos.id,
+ label: fiscalPos.name,
+ isSelected: currentFiscalPosition
+ ? fiscalPos.id === currentFiscalPosition.id
+ : false,
+ item: fiscalPos,
+ });
}
- get currentOrder() {
- return this.env.pos.get_order();
- }
- get currentFiscalPositionName() {
- return this.currentOrder && this.currentOrder.fiscal_position
- ? this.currentOrder.fiscal_position.display_name
- : this.env._t('Tax');
- }
- async onClick() {
- const currentFiscalPosition = this.currentOrder.fiscal_position;
- const fiscalPosList = [
- {
- id: -1,
- label: this.env._t('None'),
- isSelected: !currentFiscalPosition,
- },
- ];
- for (let fiscalPos of this.env.pos.fiscal_positions) {
- fiscalPosList.push({
- id: fiscalPos.id,
- label: fiscalPos.name,
- isSelected: currentFiscalPosition
- ? fiscalPos.id === currentFiscalPosition.id
- : false,
- item: fiscalPos,
- });
+ const { confirmed, payload: selectedFiscalPosition } = await this.showPopup(
+ "SelectionPopup",
+ {
+ title: this.env._t("Select Fiscal Position"),
+ list: fiscalPosList,
}
- const { confirmed, payload: selectedFiscalPosition } = await this.showPopup(
- 'SelectionPopup',
- {
- title: this.env._t('Select Fiscal Position'),
- list: fiscalPosList,
- }
- );
- if (confirmed) {
- this.currentOrder.set_fiscal_position(selectedFiscalPosition);
- // IMPROVEMENT: The following is the old implementation and I believe
- // there could be a better way of doing it.
- for (let line of this.currentOrder.orderlines) {
- line.set_quantity(line.quantity);
- }
+ );
+ if (confirmed) {
+ this.currentOrder.set_fiscal_position(selectedFiscalPosition);
+ // IMPROVEMENT: The following is the old implementation and I believe
+ // there could be a better way of doing it.
+ for (const line of this.currentOrder.orderlines) {
+ line.set_quantity(line.quantity);
}
}
}
- SetFiscalPositionButton.template = 'SetFiscalPositionButton';
+}
+SetFiscalPositionButton.template = "SetFiscalPositionButton";
- ProductScreen.addControlButton({
- component: SetFiscalPositionButton,
- condition: function() {
- return this.env.pos.fiscal_positions.length > 0;
- },
- position: ['before', 'SetPricelistButton'],
- });
-
- Registries.Component.add(SetFiscalPositionButton);
-
- return SetFiscalPositionButton;
+ProductScreen.addControlButton({
+ component: SetFiscalPositionButton,
+ condition: function () {
+ return this.env.pos.fiscal_positions.length > 0;
+ },
+ position: ["before", "SetPricelistButton"],
});
+
+Registries.Component.add(SetFiscalPositionButton);
+
+export default SetFiscalPositionButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js
index 0370543a387..80fa78c6295 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js
@@ -1,59 +1,52 @@
-odoo.define('point_of_sale.SetPricelistButton', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const ProductScreen = require('point_of_sale.ProductScreen');
- const { useListener } = require("@web/core/utils/hooks");
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
- class SetPricelistButton extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this.onClick);
- }
- get currentOrder() {
- return this.env.pos.get_order();
- }
- get currentPricelistName() {
- const order = this.currentOrder;
- return order && order.pricelist
- ? order.pricelist.display_name
- : this.env._t('Pricelist');
- }
- async onClick() {
- // Create the list to be passed to the SelectionPopup.
- // Pricelist object is passed as item in the list because it
- // is the object that will be returned when the popup is confirmed.
- const selectionList = this.env.pos.pricelists.map(pricelist => ({
- id: pricelist.id,
- label: pricelist.name,
- isSelected: pricelist.id === this.currentOrder.pricelist.id,
- item: pricelist,
- }));
+class SetPricelistButton extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this.onClick);
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ get currentPricelistName() {
+ const order = this.currentOrder;
+ return order && order.pricelist ? order.pricelist.display_name : this.env._t("Pricelist");
+ }
+ async onClick() {
+ // Create the list to be passed to the SelectionPopup.
+ // Pricelist object is passed as item in the list because it
+ // is the object that will be returned when the popup is confirmed.
+ const selectionList = this.env.pos.pricelists.map((pricelist) => ({
+ id: pricelist.id,
+ label: pricelist.name,
+ isSelected: pricelist.id === this.currentOrder.pricelist.id,
+ item: pricelist,
+ }));
- const { confirmed, payload: selectedPricelist } = await this.showPopup(
- 'SelectionPopup',
- {
- title: this.env._t('Select the pricelist'),
- list: selectionList,
- }
- );
+ const { confirmed, payload: selectedPricelist } = await this.showPopup("SelectionPopup", {
+ title: this.env._t("Select the pricelist"),
+ list: selectionList,
+ });
- if (confirmed) {
- this.currentOrder.set_pricelist(selectedPricelist);
- }
+ if (confirmed) {
+ this.currentOrder.set_pricelist(selectedPricelist);
}
}
- SetPricelistButton.template = 'SetPricelistButton';
+}
+SetPricelistButton.template = "SetPricelistButton";
- ProductScreen.addControlButton({
- component: SetPricelistButton,
- condition: function() {
- return this.env.pos.config.use_pricelist && this.env.pos.pricelists.length > 1;
- },
- });
-
- Registries.Component.add(SetPricelistButton);
-
- return SetPricelistButton;
+ProductScreen.addControlButton({
+ component: SetPricelistButton,
+ condition: function () {
+ return this.env.pos.config.use_pricelist && this.env.pos.pricelists.length > 1;
+ },
});
+
+Registries.Component.add(SetPricelistButton);
+
+export default SetPricelistButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js
index eb0e0c53826..933fccd1983 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js
@@ -1,49 +1,49 @@
-odoo.define('point_of_sale.NumpadWidget', function (require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- /**
- * @prop {'quantity' | 'price' | 'discount'} activeMode
- * @prop {Array<'quantity' | 'price' | 'discount'>} disabledModes
- * @prop {boolean} disableSign
- * @event set-numpad-mode - triggered when mode button is clicked
- * @event numpad-click-input - triggered when numpad button is clicked
- */
- class NumpadWidget extends PosComponent {
- get hasPriceControlRights() {
- return (
- this.env.pos.cashierHasPriceControlRights() &&
- !this.props.disabledModes.includes('price')
- );
- }
- get hasManualDiscount() {
- return this.env.pos.config.manual_discount && !this.props.disabledModes.includes('discount');
- }
- changeMode(mode) {
- if (!this.hasPriceControlRights && mode === 'price') {
- return;
- }
- if (!this.hasManualDiscount && mode === 'discount') {
- return;
- }
- this.trigger('set-numpad-mode', { mode });
- }
- sendInput(key) {
- this.trigger('numpad-click-input', { key });
- }
- get decimalSeparator() {
- return this.env._t.database.parameters.decimal_point;
- }
+/**
+ * @prop {'quantity' | 'price' | 'discount'} activeMode
+ * @prop {Array<'quantity' | 'price' | 'discount'>} disabledModes
+ * @prop {boolean} disableSign
+ * @event set-numpad-mode - triggered when mode button is clicked
+ * @event numpad-click-input - triggered when numpad button is clicked
+ */
+class NumpadWidget extends PosComponent {
+ get hasPriceControlRights() {
+ return (
+ this.env.pos.cashierHasPriceControlRights() &&
+ !this.props.disabledModes.includes("price")
+ );
}
- NumpadWidget.template = 'NumpadWidget';
- NumpadWidget.defaultProps = {
- disabledModes: [],
- disableSign: false,
+ get hasManualDiscount() {
+ return (
+ this.env.pos.config.manual_discount && !this.props.disabledModes.includes("discount")
+ );
}
+ changeMode(mode) {
+ if (!this.hasPriceControlRights && mode === "price") {
+ return;
+ }
+ if (!this.hasManualDiscount && mode === "discount") {
+ return;
+ }
+ this.trigger("set-numpad-mode", { mode });
+ }
+ sendInput(key) {
+ this.trigger("numpad-click-input", { key });
+ }
+ get decimalSeparator() {
+ return this.env._t.database.parameters.decimal_point;
+ }
+}
+NumpadWidget.template = "NumpadWidget";
+NumpadWidget.defaultProps = {
+ disabledModes: [],
+ disableSign: false,
+};
- Registries.Component.add(NumpadWidget);
+Registries.Component.add(NumpadWidget);
- return NumpadWidget;
-});
+export default NumpadWidget;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js
index 854e71e9274..b77f9060b10 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js
@@ -1,27 +1,25 @@
-odoo.define('point_of_sale.OrderSummary', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
- const { float_is_zero } = require('web.utils');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
+import { float_is_zero } from "web.utils";
- class OrderSummary extends PosComponent {
- getTotal() {
- return this.env.pos.format_currency(this.props.order.get_total_with_tax());
- }
- getTax() {
- const total = this.props.order.get_total_with_tax();
- const totalWithoutTax = this.props.order.get_total_without_tax();
- const taxAmount = total - totalWithoutTax;
- return {
- hasTax: !float_is_zero(taxAmount, this.env.pos.currency.decimal_places),
- displayAmount: this.env.pos.format_currency(taxAmount),
- };
- }
+class OrderSummary extends PosComponent {
+ getTotal() {
+ return this.env.pos.format_currency(this.props.order.get_total_with_tax());
}
- OrderSummary.template = 'OrderSummary';
+ getTax() {
+ const total = this.props.order.get_total_with_tax();
+ const totalWithoutTax = this.props.order.get_total_without_tax();
+ const taxAmount = total - totalWithoutTax;
+ return {
+ hasTax: !float_is_zero(taxAmount, this.env.pos.currency.decimal_places),
+ displayAmount: this.env.pos.format_currency(taxAmount),
+ };
+ }
+}
+OrderSummary.template = "OrderSummary";
- Registries.Component.add(OrderSummary);
+Registries.Component.add(OrderSummary);
- return OrderSummary;
-});
+export default OrderSummary;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js
index c6b241fc878..4eb0edb2b6c 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js
@@ -1,72 +1,70 @@
-odoo.define('point_of_sale.OrderWidget', function(require) {
- 'use strict';
+/** @odoo-module */
- const { useListener } = require("@web/core/utils/hooks");
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { useListener } from "@web/core/utils/hooks";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { onPatched, useRef } = owl;
+const { onPatched, useRef } = owl;
- class OrderWidget extends PosComponent {
- setup() {
- super.setup();
- useListener('select-line', this._selectLine);
- useListener('edit-pack-lot-lines', this._editPackLotLines);
- this.scrollableRef = useRef('scrollable');
- this.scrollToBottom = false;
- onPatched(() => {
- // IMPROVEMENT
- // This one just stays at the bottom of the orderlines list.
- // Perhaps it is better to scroll to the added or modified orderline.
- if (this.scrollToBottom) {
- this.scrollableRef.el.scrollTop = this.scrollableRef.el.scrollHeight;
- this.scrollToBottom = false;
- }
- });
- }
- get order() {
- return this.env.pos.get_order();
- }
- get orderlinesArray() {
- return this.order ? this.order.get_orderlines() : [];
- }
- _selectLine(event) {
- this.order.select_orderline(event.detail.orderline);
- }
- // IMPROVEMENT: Might be better to lift this to ProductScreen
- // because there is similar operation when clicking a product.
- //
- // Furthermore, what if a number different from 1 (or -1) is specified
- // to an orderline that has product tracked by lot. Lot tracking (based
- // on the current implementation) requires that 1 item per orderline is
- // allowed.
- async _editPackLotLines(event) {
- const orderline = event.detail.orderline;
- const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot();
- const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot);
- const { confirmed, payload } = await this.showPopup('EditListPopup', {
- title: this.env._t('Lot/Serial Number(s) Required'),
- name: orderline.product.display_name,
- isSingleItem: isAllowOnlyOneLot,
- array: packLotLinesToEdit,
- });
- if (confirmed) {
- // Segregate the old and new packlot lines
- const modifiedPackLotLines = Object.fromEntries(
- payload.newArray.filter(item => item.id).map(item => [item.id, item.text])
- );
- const newPackLotLines = payload.newArray
- .filter(item => !item.id)
- .map(item => ({ lot_name: item.text }));
-
- orderline.setPackLotLines({ modifiedPackLotLines, newPackLotLines });
+class OrderWidget extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("select-line", this._selectLine);
+ useListener("edit-pack-lot-lines", this._editPackLotLines);
+ this.scrollableRef = useRef("scrollable");
+ this.scrollToBottom = false;
+ onPatched(() => {
+ // IMPROVEMENT
+ // This one just stays at the bottom of the orderlines list.
+ // Perhaps it is better to scroll to the added or modified orderline.
+ if (this.scrollToBottom) {
+ this.scrollableRef.el.scrollTop = this.scrollableRef.el.scrollHeight;
+ this.scrollToBottom = false;
}
- this.order.select_orderline(event.detail.orderline);
- }
+ });
}
- OrderWidget.template = 'OrderWidget';
+ get order() {
+ return this.env.pos.get_order();
+ }
+ get orderlinesArray() {
+ return this.order ? this.order.get_orderlines() : [];
+ }
+ _selectLine(event) {
+ this.order.select_orderline(event.detail.orderline);
+ }
+ // IMPROVEMENT: Might be better to lift this to ProductScreen
+ // because there is similar operation when clicking a product.
+ //
+ // Furthermore, what if a number different from 1 (or -1) is specified
+ // to an orderline that has product tracked by lot. Lot tracking (based
+ // on the current implementation) requires that 1 item per orderline is
+ // allowed.
+ async _editPackLotLines(event) {
+ const orderline = event.detail.orderline;
+ const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot();
+ const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot);
+ const { confirmed, payload } = await this.showPopup("EditListPopup", {
+ title: this.env._t("Lot/Serial Number(s) Required"),
+ name: orderline.product.display_name,
+ isSingleItem: isAllowOnlyOneLot,
+ array: packLotLinesToEdit,
+ });
+ if (confirmed) {
+ // Segregate the old and new packlot lines
+ const modifiedPackLotLines = Object.fromEntries(
+ payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text])
+ );
+ const newPackLotLines = payload.newArray
+ .filter((item) => !item.id)
+ .map((item) => ({ lot_name: item.text }));
- Registries.Component.add(OrderWidget);
+ orderline.setPackLotLines({ modifiedPackLotLines, newPackLotLines });
+ }
+ this.order.select_orderline(event.detail.orderline);
+ }
+}
+OrderWidget.template = "OrderWidget";
- return OrderWidget;
-});
+Registries.Component.add(OrderWidget);
+
+export default OrderWidget;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js
index 44213f0b956..29aa8ee0126 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js
@@ -1,28 +1,26 @@
-odoo.define('point_of_sale.Orderline', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class Orderline extends PosComponent {
- selectLine() {
- this.trigger('select-line', { orderline: this.props.line });
- }
- lotIconClicked() {
- this.trigger('edit-pack-lot-lines', { orderline: this.props.line });
- }
- get addedClasses() {
- return {
- selected: this.props.line.selected,
- };
- }
- get customerNote() {
- return this.props.line.get_customer_note();
- }
+class Orderline extends PosComponent {
+ selectLine() {
+ this.trigger("select-line", { orderline: this.props.line });
}
- Orderline.template = 'Orderline';
+ lotIconClicked() {
+ this.trigger("edit-pack-lot-lines", { orderline: this.props.line });
+ }
+ get addedClasses() {
+ return {
+ selected: this.props.line.selected,
+ };
+ }
+ get customerNote() {
+ return this.props.line.get_customer_note();
+ }
+}
+Orderline.template = "Orderline";
- Registries.Component.add(Orderline);
+Registries.Component.add(Orderline);
- return Orderline;
-});
+export default Orderline;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js
index 17f9f2734b8..87a42cf4bc4 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js
@@ -1,49 +1,47 @@
-odoo.define('point_of_sale.ProductItem', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class ProductItem extends PosComponent {
- /**
- * For accessibility, pressing should be like clicking the product.
- * is not considered because it conflicts with the barcode.
- *
- * @param {KeyPressEvent} event
- */
- spaceClickProduct(event) {
- if (event.which === 32) {
- this.trigger('click-product', this.props.product);
- }
- }
- get imageUrl() {
- const product = this.props.product;
- return `/web/image?model=product.product&field=image_128&id=${product.id}&unique=${product.write_date}`;
- }
- get pricelist() {
- const current_order = this.env.pos.get_order();
- if (current_order) {
- return current_order.pricelist;
- }
- return this.env.pos.default_pricelist;
- }
- get price() {
- const formattedUnitPrice = this.env.pos.format_currency(
- this.props.product.get_display_price(this.pricelist, 1),
- 'Product Price'
- );
- if (this.props.product.to_weight) {
- return `${formattedUnitPrice}/${
- this.env.pos.units_by_id[this.props.product.uom_id[0]].name
- }`;
- } else {
- return formattedUnitPrice;
- }
+class ProductItem extends PosComponent {
+ /**
+ * For accessibility, pressing should be like clicking the product.
+ * is not considered because it conflicts with the barcode.
+ *
+ * @param {KeyPressEvent} event
+ */
+ spaceClickProduct(event) {
+ if (event.which === 32) {
+ this.trigger("click-product", this.props.product);
}
}
- ProductItem.template = 'ProductItem';
+ get imageUrl() {
+ const product = this.props.product;
+ return `/web/image?model=product.product&field=image_128&id=${product.id}&unique=${product.write_date}`;
+ }
+ get pricelist() {
+ const current_order = this.env.pos.get_order();
+ if (current_order) {
+ return current_order.pricelist;
+ }
+ return this.env.pos.default_pricelist;
+ }
+ get price() {
+ const formattedUnitPrice = this.env.pos.format_currency(
+ this.props.product.get_display_price(this.pricelist, 1),
+ "Product Price"
+ );
+ if (this.props.product.to_weight) {
+ return `${formattedUnitPrice}/${
+ this.env.pos.units_by_id[this.props.product.uom_id[0]].name
+ }`;
+ } else {
+ return formattedUnitPrice;
+ }
+ }
+}
+ProductItem.template = "ProductItem";
- Registries.Component.add(ProductItem);
+Registries.Component.add(ProductItem);
- return ProductItem;
-});
+export default ProductItem;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js
index 1cba86074e5..21da9bb4321 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js
@@ -1,390 +1,421 @@
-odoo.define('point_of_sale.ProductScreen', function(require) {
- 'use strict';
+/** @odoo-module */
- 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/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');
- const { parse } = require('web.field_utils');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import ControlButtonsMixin from "@point_of_sale/js/ControlButtonsMixin";
+import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
+import { useBarcodeReader } from "@point_of_sale/js/custom_hooks";
+import { isConnectionError } from "@point_of_sale/js/utils";
+import { parse } from "web.field_utils";
- const { onMounted, useState } = owl;
+const { onMounted, useState } = owl;
- class ProductScreen extends ControlButtonsMixin(PosComponent) {
- setup() {
- super.setup();
- useListener('update-selected-orderline', this._updateSelectedOrderline);
- useListener('select-line', this._selectLine);
- useListener('set-numpad-mode', this._setNumpadMode);
- useListener('click-product', this._clickProduct);
- useListener('click-partner', this.onClickPartner);
- useListener('click-pay', this._onClickPay);
- useBarcodeReader({
- product: this._barcodeProductAction,
- weight: this._barcodeProductAction,
- price: this._barcodeProductAction,
- client: this._barcodePartnerAction,
- discount: this._barcodeDiscountAction,
- error: this._barcodeErrorAction,
+class ProductScreen extends ControlButtonsMixin(PosComponent) {
+ setup() {
+ super.setup();
+ useListener("update-selected-orderline", this._updateSelectedOrderline);
+ useListener("select-line", this._selectLine);
+ useListener("set-numpad-mode", this._setNumpadMode);
+ useListener("click-product", this._clickProduct);
+ useListener("click-partner", this.onClickPartner);
+ useListener("click-pay", this._onClickPay);
+ useBarcodeReader({
+ product: this._barcodeProductAction,
+ weight: this._barcodeProductAction,
+ price: this._barcodeProductAction,
+ client: this._barcodePartnerAction,
+ discount: this._barcodeDiscountAction,
+ error: this._barcodeErrorAction,
+ });
+ NumberBuffer.use({
+ nonKeyboardInputEvent: "numpad-click-input",
+ 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.
+ onMounted(() => NumberBuffer.reset());
+ this.state = useState({
+ mobile_pane: this.props.mobile_pane || "right",
+ });
+ }
+ onMounted() {
+ this.env.posbus.trigger("start-cash-control");
+ }
+ /**
+ * To be overridden by modules that checks availability of
+ * connected scale.
+ * @see _onScaleNotAvailable
+ */
+ get isScaleAvailable() {
+ return true;
+ }
+ get partner() {
+ return this.currentOrder ? this.currentOrder.get_partner() : null;
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ async _getAddProductOptions(product, base_code) {
+ let price_extra = 0.0;
+ let draftPackLotLines, weight, description, packLotLinesToEdit;
+
+ if (_.some(product.attribute_line_ids, (id) => id in this.env.pos.attributes_by_ptal_id)) {
+ const attributes = _.map(
+ product.attribute_line_ids,
+ (id) => this.env.pos.attributes_by_ptal_id[id]
+ ).filter((attr) => attr !== undefined);
+ const { confirmed, payload } = await this.showPopup("ProductConfiguratorPopup", {
+ product: product,
+ attributes: attributes,
});
- NumberBuffer.use({
- nonKeyboardInputEvent: 'numpad-click-input',
- 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.
- onMounted(() => NumberBuffer.reset());
- this.state = useState({
- mobile_pane: this.props.mobile_pane || 'right',
- });
- }
- onMounted() {
- this.env.posbus.trigger('start-cash-control');
- }
- /**
- * To be overridden by modules that checks availability of
- * connected scale.
- * @see _onScaleNotAvailable
- */
- get isScaleAvailable() {
- return true;
- }
- get partner() {
- return this.currentOrder ? this.currentOrder.get_partner() : null;
- }
- get currentOrder() {
- return this.env.pos.get_order();
- }
- async _getAddProductOptions(product, base_code) {
- let price_extra = 0.0;
- let draftPackLotLines, weight, description, packLotLinesToEdit;
- if (_.some(product.attribute_line_ids, (id) => id in this.env.pos.attributes_by_ptal_id)) {
- let attributes = _.map(product.attribute_line_ids, (id) => this.env.pos.attributes_by_ptal_id[id])
- .filter((attr) => attr !== undefined);
- let { confirmed, payload } = await this.showPopup('ProductConfiguratorPopup', {
- product: product,
- attributes: attributes,
- });
-
- if (confirmed) {
- description = payload.selected_attributes.join(', ');
- price_extra += payload.price_extra;
- } else {
- return;
- }
+ if (confirmed) {
+ description = payload.selected_attributes.join(", ");
+ price_extra += payload.price_extra;
+ } else {
+ return;
}
+ }
- // Gather lot information if required.
- if (['serial', 'lot'].includes(product.tracking) && (this.env.pos.picking_type.use_create_lots || this.env.pos.picking_type.use_existing_lots)) {
- const isAllowOnlyOneLot = product.isAllowOnlyOneLot();
- if (isAllowOnlyOneLot) {
+ // Gather lot information if required.
+ if (
+ ["serial", "lot"].includes(product.tracking) &&
+ (this.env.pos.picking_type.use_create_lots ||
+ this.env.pos.picking_type.use_existing_lots)
+ ) {
+ const isAllowOnlyOneLot = product.isAllowOnlyOneLot();
+ if (isAllowOnlyOneLot) {
+ packLotLinesToEdit = [];
+ } else {
+ const orderline = this.currentOrder
+ .get_orderlines()
+ .filter((line) => !line.get_discount())
+ .find((line) => line.product.id === product.id);
+ if (orderline) {
+ packLotLinesToEdit = orderline.getPackLotLinesToEdit();
+ } else {
packLotLinesToEdit = [];
- } else {
- const orderline = this.currentOrder
- .get_orderlines()
- .filter(line => !line.get_discount())
- .find(line => line.product.id === product.id);
- if (orderline) {
- packLotLinesToEdit = orderline.getPackLotLinesToEdit();
- } else {
- packLotLinesToEdit = [];
- }
}
- const { confirmed, payload } = await this.showPopup('EditListPopup', {
- title: this.env._t('Lot/Serial Number(s) Required'),
- name: product.display_name,
- isSingleItem: isAllowOnlyOneLot,
- array: packLotLinesToEdit,
+ }
+ const { confirmed, payload } = await this.showPopup("EditListPopup", {
+ title: this.env._t("Lot/Serial Number(s) Required"),
+ name: product.display_name,
+ isSingleItem: isAllowOnlyOneLot,
+ array: packLotLinesToEdit,
+ });
+ if (confirmed) {
+ // Segregate the old and new packlot lines
+ const modifiedPackLotLines = Object.fromEntries(
+ payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text])
+ );
+ const newPackLotLines = payload.newArray
+ .filter((item) => !item.id)
+ .map((item) => ({ lot_name: item.text }));
+
+ draftPackLotLines = { modifiedPackLotLines, newPackLotLines };
+ } else {
+ // We don't proceed on adding product.
+ return;
+ }
+ }
+
+ // Take the weight if necessary.
+ if (product.to_weight && this.env.pos.config.iface_electronic_scale) {
+ // Show the ScaleScreen to weigh the product.
+ if (this.isScaleAvailable) {
+ const { confirmed, payload } = await this.showTempScreen("ScaleScreen", {
+ product,
});
if (confirmed) {
- // Segregate the old and new packlot lines
- const modifiedPackLotLines = Object.fromEntries(
- payload.newArray.filter(item => item.id).map(item => [item.id, item.text])
- );
- const newPackLotLines = payload.newArray
- .filter(item => !item.id)
- .map(item => ({ lot_name: item.text }));
-
- draftPackLotLines = { modifiedPackLotLines, newPackLotLines };
+ weight = payload.weight;
} else {
- // We don't proceed on adding product.
+ // do not add the product;
return;
}
- }
-
- // Take the weight if necessary.
- if (product.to_weight && this.env.pos.config.iface_electronic_scale) {
- // Show the ScaleScreen to weigh the product.
- if (this.isScaleAvailable) {
- const { confirmed, payload } = await this.showTempScreen('ScaleScreen', {
- product,
- });
- if (confirmed) {
- weight = payload.weight;
- } else {
- // do not add the product;
- return;
- }
- } else {
- await this._onScaleNotAvailable();
- }
- }
-
- if (base_code && this.env.pos.db.product_packaging_by_barcode[base_code.code]) {
- weight = this.env.pos.db.product_packaging_by_barcode[base_code.code].qty;
- }
-
- return { draftPackLotLines, quantity: weight, description, price_extra };
- }
- async _addProduct(product, options) {
- this.currentOrder.add_product(product, options);
- }
- async _clickProduct(event) {
- if (!this.currentOrder) {
- this.env.pos.add_new_order();
- }
- const product = event.detail;
- const options = await this._getAddProductOptions(product);
- // Do not add product if options is undefined.
- if (!options) return;
- // Add the product after having the extra information.
- await this._addProduct(product, options);
- NumberBuffer.reset();
- }
- _setNumpadMode(event) {
- const { mode } = event.detail;
- NumberBuffer.capture();
- NumberBuffer.reset();
- this.env.pos.numpadMode = mode;
- }
- _selectLine() {
- NumberBuffer.reset();
- }
- async _updateSelectedOrderline(event) {
- if (this.env.pos.numpadMode === 'quantity' && this.env.pos.disallowLineQuantityChange()) {
- let order = this.env.pos.get_order();
- if(!order.orderlines.length)
- return;
- let selectedLine = order.get_selected_orderline();
- let orderlines = order.orderlines;
- let lastId = orderlines.length !== 0 && orderlines.at(orderlines.length - 1).cid;
- let currentQuantity = this.env.pos.get_order().get_selected_orderline().get_quantity();
-
- if(selectedLine.noDecrease) {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Invalid action'),
- body: this.env._t('You are not allowed to change this quantity'),
- });
- return;
- }
- const parsedInput = event.detail.buffer && parse.float(event.detail.buffer) || 0;
- if(lastId != selectedLine.cid)
- this._showDecreaseQuantityPopup();
- else if(currentQuantity < parsedInput)
- this._setValue(event.detail.buffer);
- else if(parsedInput < currentQuantity)
- this._showDecreaseQuantityPopup();
} else {
- let { buffer } = event.detail;
- let val = buffer === null ? 'remove' : buffer;
- this._setValue(val);
- if (val == 'remove') {
- NumberBuffer.reset();
- this.env.pos.numpadMode = 'quantity';
- }
+ await this._onScaleNotAvailable();
}
}
- _setValue(val) {
- if (this.currentOrder.get_selected_orderline()) {
- if (this.env.pos.numpadMode === 'quantity') {
- const result = this.currentOrder.get_selected_orderline().set_quantity(val);
- if (!result) NumberBuffer.reset();
- } else if (this.env.pos.numpadMode === 'discount') {
- this.currentOrder.get_selected_orderline().set_discount(val);
- } else if (this.env.pos.numpadMode === 'price') {
- var selected_orderline = this.currentOrder.get_selected_orderline();
- selected_orderline.price_manually_set = true;
- selected_orderline.set_unit_price(val);
- }
- }
- }
- async _barcodeProductAction(code) {
- let product = this.env.pos.db.get_product_by_barcode(code.base_code);
- if (!product) {
- // find the barcode in the backend
- let foundProductIds = [];
- try {
- foundProductIds = await this.rpc({
- model: 'product.product',
- method: 'search',
- args: [[['barcode', '=', code.base_code]]],
- context: this.env.session.user_context,
- });
- } catch (error) {
- if (isConnectionError(error)) {
- return this.showPopup('OfflineErrorPopup', {
- title: this.env._t('Network Error'),
- body: this.env._t("Product is not loaded. Tried loading the product from the server but there is a network error."),
- });
- } else {
- throw error;
- }
- }
- if (foundProductIds.length) {
- await this.env.pos._addProducts(foundProductIds);
- // assume that the result is unique.
- product = this.env.pos.db.get_product_by_id(foundProductIds[0]);
- } else {
- return this._barcodeErrorAction(code);
- }
- }
- const options = await this._getAddProductOptions(product, code);
- // Do not proceed on adding the product when no options is returned.
- // This is consistent with _clickProduct.
- if (!options) return;
- // update the options depending on the type of the scanned code
- if (code.type === 'price') {
- Object.assign(options, {
- price: code.value,
- extras: {
- price_manually_set: true,
- },
- });
- } else if (code.type === 'weight') {
- Object.assign(options, {
- quantity: code.value,
- merge: false,
- });
- } else if (code.type === 'discount') {
- Object.assign(options, {
- discount: code.value,
- merge: false,
- });
- }
- this.currentOrder.add_product(product, options)
+ if (base_code && this.env.pos.db.product_packaging_by_barcode[base_code.code]) {
+ weight = this.env.pos.db.product_packaging_by_barcode[base_code.code].qty;
}
- _barcodePartnerAction(code) {
- const partner = this.env.pos.db.get_partner_by_barcode(code.code);
- if (partner) {
- if (this.currentOrder.get_partner() !== partner) {
- this.currentOrder.set_partner(partner);
- this.currentOrder.set_pricelist(
- _.findWhere(this.env.pos.pricelists, {
- id: partner.property_product_pricelist[0],
- }) || this.env.pos.default_pricelist
- );
- }
- return true;
- }
- this._barcodeErrorAction(code);
- return false;
- }
- _barcodeDiscountAction(code) {
- var last_orderline = this.currentOrder.get_last_orderline();
- if (last_orderline) {
- last_orderline.set_discount(code.value);
- }
- }
- // IMPROVEMENT: The following two methods should be in PosScreenComponent?
- // Why? Because once we start declaring barcode actions in different
- // screens, these methods will also be declared over and over.
- _barcodeErrorAction(code) {
- this.showPopup('ErrorBarcodePopup', { code: this._codeRepr(code) });
- }
- _codeRepr(code) {
- if (code.code.length > 32) {
- return code.code.substring(0, 29) + '...';
- } else {
- return code.code;
- }
- }
- async _displayAllControlPopup() {
- await this.showPopup('ControlButtonPopup', {
- controlButtons: this.controlButtons
- });
- }
- /**
- * override this method to perform procedure if the scale is not available.
- * @see isScaleAvailable
- */
- async _onScaleNotAvailable() {}
- async _showDecreaseQuantityPopup() {
- const { confirmed, payload: inputNumber } = await this.showPopup('NumberPopup', {
- startingValue: 0,
- title: this.env._t('Set the new quantity'),
- });
- let newQuantity = inputNumber && inputNumber !== "" ? parse.float(inputNumber) : null;
- if (confirmed && newQuantity !== null) {
- let order = this.env.pos.get_order();
- let selectedLine = this.env.pos.get_order().get_selected_orderline();
- let currentQuantity = selectedLine.get_quantity()
- if(selectedLine.is_last_line() && currentQuantity === 1 && newQuantity < currentQuantity)
- selectedLine.set_quantity(newQuantity);
- else if(newQuantity >= currentQuantity)
- selectedLine.set_quantity(newQuantity);
- else {
- let newLine = selectedLine.clone();
- let decreasedQuantity = currentQuantity - newQuantity
- newLine.order = order;
- newLine.set_quantity( - decreasedQuantity, true);
- order.add_orderline(newLine);
- }
- }
+ return { draftPackLotLines, quantity: weight, description, price_extra };
+ }
+ async _addProduct(product, options) {
+ this.currentOrder.add_product(product, options);
+ }
+ async _clickProduct(event) {
+ if (!this.currentOrder) {
+ this.env.pos.add_new_order();
}
- async onClickPartner() {
- // IMPROVEMENT: This code snippet is very similar to selectPartner of PaymentScreen.
- const currentPartner = this.currentOrder.get_partner();
- if (currentPartner && this.currentOrder.getHasRefundLines()) {
- this.showPopup('ErrorPopup', {
- title: this.env._t("Can't change customer"),
- body: _.str.sprintf(
- this.env._t(
- "This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer."
- ),
- currentPartner.name
- ),
+ const product = event.detail;
+ const options = await this._getAddProductOptions(product);
+ // Do not add product if options is undefined.
+ if (!options) {
+ return;
+ }
+ // Add the product after having the extra information.
+ await this._addProduct(product, options);
+ NumberBuffer.reset();
+ }
+ _setNumpadMode(event) {
+ const { mode } = event.detail;
+ NumberBuffer.capture();
+ NumberBuffer.reset();
+ this.env.pos.numpadMode = mode;
+ }
+ _selectLine() {
+ NumberBuffer.reset();
+ }
+ async _updateSelectedOrderline(event) {
+ if (this.env.pos.numpadMode === "quantity" && this.env.pos.disallowLineQuantityChange()) {
+ const order = this.env.pos.get_order();
+ if (!order.orderlines.length) {
+ return;
+ }
+ const selectedLine = order.get_selected_orderline();
+ const orderlines = order.orderlines;
+ const lastId = orderlines.length !== 0 && orderlines.at(orderlines.length - 1).cid;
+ const currentQuantity = this.env.pos
+ .get_order()
+ .get_selected_orderline()
+ .get_quantity();
+
+ if (selectedLine.noDecrease) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Invalid action"),
+ body: this.env._t("You are not allowed to change this quantity"),
});
return;
}
- const { confirmed, payload: newPartner } = await this.showTempScreen(
- 'PartnerListScreen',
- { partner: currentPartner }
- );
- if (confirmed) {
- this.currentOrder.set_partner(newPartner);
- this.currentOrder.updatePricelist(newPartner);
+ const parsedInput = (event.detail.buffer && parse.float(event.detail.buffer)) || 0;
+ if (lastId != selectedLine.cid) {
+ this._showDecreaseQuantityPopup();
+ } else if (currentQuantity < parsedInput) {
+ this._setValue(event.detail.buffer);
+ } else if (parsedInput < currentQuantity) {
+ this._showDecreaseQuantityPopup();
}
- }
- async _onClickPay() {
- if (this.env.pos.get_order().orderlines.some(line => line.get_product().tracking !== 'none' && !line.has_valid_product_lot()) && (this.env.pos.picking_type.use_create_lots || this.env.pos.picking_type.use_existing_lots)) {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Some Serial/Lot Numbers are missing'),
- body: this.env._t('You are trying to sell products with serial/lot numbers, but some of them are not set.\nWould you like to proceed anyway?'),
- confirmText: this.env._t('Yes'),
- cancelText: this.env._t('No')
- });
- if (confirmed) {
- this.showScreen('PaymentScreen');
- }
- } else {
- this.showScreen('PaymentScreen');
+ } else {
+ const { buffer } = event.detail;
+ const val = buffer === null ? "remove" : buffer;
+ this._setValue(val);
+ if (val == "remove") {
+ NumberBuffer.reset();
+ this.env.pos.numpadMode = "quantity";
}
}
- switchPane() {
- this.state.mobile_pane = this.state.mobile_pane === "left" ? "right" : "left";
- }
}
- ProductScreen.template = 'ProductScreen';
+ _setValue(val) {
+ if (this.currentOrder.get_selected_orderline()) {
+ if (this.env.pos.numpadMode === "quantity") {
+ const result = this.currentOrder.get_selected_orderline().set_quantity(val);
+ if (!result) {
+ NumberBuffer.reset();
+ }
+ } else if (this.env.pos.numpadMode === "discount") {
+ this.currentOrder.get_selected_orderline().set_discount(val);
+ } else if (this.env.pos.numpadMode === "price") {
+ var selected_orderline = this.currentOrder.get_selected_orderline();
+ selected_orderline.price_manually_set = true;
+ selected_orderline.set_unit_price(val);
+ }
+ }
+ }
+ async _barcodeProductAction(code) {
+ let product = this.env.pos.db.get_product_by_barcode(code.base_code);
+ if (!product) {
+ // find the barcode in the backend
+ let foundProductIds = [];
+ try {
+ foundProductIds = await this.rpc({
+ model: "product.product",
+ method: "search",
+ args: [[["barcode", "=", code.base_code]]],
+ context: this.env.session.user_context,
+ });
+ } catch (error) {
+ if (isConnectionError(error)) {
+ return this.showPopup("OfflineErrorPopup", {
+ title: this.env._t("Network Error"),
+ body: this.env._t(
+ "Product is not loaded. Tried loading the product from the server but there is a network error."
+ ),
+ });
+ } else {
+ throw error;
+ }
+ }
+ if (foundProductIds.length) {
+ await this.env.pos._addProducts(foundProductIds);
+ // assume that the result is unique.
+ product = this.env.pos.db.get_product_by_id(foundProductIds[0]);
+ } else {
+ return this._barcodeErrorAction(code);
+ }
+ }
+ const options = await this._getAddProductOptions(product, code);
+ // Do not proceed on adding the product when no options is returned.
+ // This is consistent with _clickProduct.
+ if (!options) {
+ return;
+ }
- Registries.Component.add(ProductScreen);
+ // update the options depending on the type of the scanned code
+ if (code.type === "price") {
+ Object.assign(options, {
+ price: code.value,
+ extras: {
+ price_manually_set: true,
+ },
+ });
+ } else if (code.type === "weight") {
+ Object.assign(options, {
+ quantity: code.value,
+ merge: false,
+ });
+ } else if (code.type === "discount") {
+ Object.assign(options, {
+ discount: code.value,
+ merge: false,
+ });
+ }
+ this.currentOrder.add_product(product, options);
+ }
+ _barcodePartnerAction(code) {
+ const partner = this.env.pos.db.get_partner_by_barcode(code.code);
+ if (partner) {
+ if (this.currentOrder.get_partner() !== partner) {
+ this.currentOrder.set_partner(partner);
+ this.currentOrder.set_pricelist(
+ _.findWhere(this.env.pos.pricelists, {
+ id: partner.property_product_pricelist[0],
+ }) || this.env.pos.default_pricelist
+ );
+ }
+ return true;
+ }
+ this._barcodeErrorAction(code);
+ return false;
+ }
+ _barcodeDiscountAction(code) {
+ var last_orderline = this.currentOrder.get_last_orderline();
+ if (last_orderline) {
+ last_orderline.set_discount(code.value);
+ }
+ }
+ // IMPROVEMENT: The following two methods should be in PosScreenComponent?
+ // Why? Because once we start declaring barcode actions in different
+ // screens, these methods will also be declared over and over.
+ _barcodeErrorAction(code) {
+ this.showPopup("ErrorBarcodePopup", { code: this._codeRepr(code) });
+ }
+ _codeRepr(code) {
+ if (code.code.length > 32) {
+ return code.code.substring(0, 29) + "...";
+ } else {
+ return code.code;
+ }
+ }
+ async _displayAllControlPopup() {
+ await this.showPopup("ControlButtonPopup", {
+ controlButtons: this.controlButtons,
+ });
+ }
+ /**
+ * override this method to perform procedure if the scale is not available.
+ * @see isScaleAvailable
+ */
+ async _onScaleNotAvailable() {}
+ async _showDecreaseQuantityPopup() {
+ const { confirmed, payload: inputNumber } = await this.showPopup("NumberPopup", {
+ startingValue: 0,
+ title: this.env._t("Set the new quantity"),
+ });
+ const newQuantity = inputNumber && inputNumber !== "" ? parse.float(inputNumber) : null;
+ if (confirmed && newQuantity !== null) {
+ const order = this.env.pos.get_order();
+ const selectedLine = this.env.pos.get_order().get_selected_orderline();
+ const currentQuantity = selectedLine.get_quantity();
+ if (
+ selectedLine.is_last_line() &&
+ currentQuantity === 1 &&
+ newQuantity < currentQuantity
+ ) {
+ selectedLine.set_quantity(newQuantity);
+ } else if (newQuantity >= currentQuantity) {
+ selectedLine.set_quantity(newQuantity);
+ } else {
+ const newLine = selectedLine.clone();
+ const decreasedQuantity = currentQuantity - newQuantity;
+ newLine.order = order;
- return ProductScreen;
-});
+ newLine.set_quantity(-decreasedQuantity, true);
+ order.add_orderline(newLine);
+ }
+ }
+ }
+ async onClickPartner() {
+ // IMPROVEMENT: This code snippet is very similar to selectPartner of PaymentScreen.
+ const currentPartner = this.currentOrder.get_partner();
+ if (currentPartner && this.currentOrder.getHasRefundLines()) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Can't change customer"),
+ body: _.str.sprintf(
+ this.env._t(
+ "This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer."
+ ),
+ currentPartner.name
+ ),
+ });
+ return;
+ }
+ const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", {
+ partner: currentPartner,
+ });
+ if (confirmed) {
+ this.currentOrder.set_partner(newPartner);
+ this.currentOrder.updatePricelist(newPartner);
+ }
+ }
+ async _onClickPay() {
+ if (
+ this.env.pos
+ .get_order()
+ .orderlines.some(
+ (line) =>
+ line.get_product().tracking !== "none" && !line.has_valid_product_lot()
+ ) &&
+ (this.env.pos.picking_type.use_create_lots ||
+ this.env.pos.picking_type.use_existing_lots)
+ ) {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Some Serial/Lot Numbers are missing"),
+ body: this.env._t(
+ "You are trying to sell products with serial/lot numbers, but some of them are not set.\nWould you like to proceed anyway?"
+ ),
+ confirmText: this.env._t("Yes"),
+ cancelText: this.env._t("No"),
+ });
+ if (confirmed) {
+ this.showScreen("PaymentScreen");
+ }
+ } else {
+ this.showScreen("PaymentScreen");
+ }
+ }
+ switchPane() {
+ this.state.mobile_pane = this.state.mobile_pane === "left" ? "right" : "left";
+ }
+}
+ProductScreen.template = "ProductScreen";
+
+Registries.Component.add(ProductScreen);
+
+export default ProductScreen;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js
index 17b9988f3ce..e5d027994a8 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js
@@ -1,84 +1,88 @@
-odoo.define('point_of_sale.ProductsWidget', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const { useListener } = require("@web/core/utils/hooks");
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import { useListener } from "@web/core/utils/hooks";
+import Registries from "@point_of_sale/js/Registries";
- const { onWillUnmount, useState } = owl;
+const { onWillUnmount, useState } = owl;
- class ProductsWidget extends PosComponent {
- /**
- * @param {Object} props
- * @param {number?} props.startCategoryId
- */
- setup() {
- super.setup();
- useListener('switch-category', this._switchCategory);
- useListener('update-search', this._updateSearch);
- useListener('clear-search', this._clearSearch);
- useListener('update-product-list', this._updateProductList);
- this.state = useState({ searchWord: '' });
- onWillUnmount(this.onWillUnmount);
- }
- onWillUnmount() {
- this.trigger('toggle-mobile-searchbar', false);
- }
- get selectedCategoryId() {
- return this.env.pos.selectedCategoryId;
- }
- get searchWord() {
- return this.state.searchWord.trim();
- }
- get productsToDisplay() {
- let list = [];
- if (this.searchWord !== '') {
- list = this.env.pos.db.search_product_in_category(
- this.selectedCategoryId,
- this.searchWord
- );
- } else {
- list = this.env.pos.db.get_product_by_category(this.selectedCategoryId);
- }
- return list.sort(function (a, b) { return a.display_name.localeCompare(b.display_name) });
- }
- get subcategories() {
- return this.env.pos.db
- .get_category_childs_ids(this.selectedCategoryId)
- .map(id => this.env.pos.db.get_category_by_id(id));
- }
- get breadcrumbs() {
- if (this.selectedCategoryId === this.env.pos.db.root_category_id) return [];
- return [
- ...this.env.pos.db
- .get_category_ancestors_ids(this.selectedCategoryId)
- .slice(1),
- this.selectedCategoryId,
- ].map(id => this.env.pos.db.get_category_by_id(id));
- }
- get hasNoCategories() {
- return this.env.pos.db.get_category_childs_ids(0).length === 0;
- }
- get shouldShowButton() {
- return this.productsToDisplay.length === 0 && this.searchWord && !this.env.pos.isEveryProductLoaded;
- }
- _switchCategory(event) {
- this.env.pos.setSelectedCategoryId(event.detail);
- }
- _updateSearch(event) {
- this.state.searchWord = event.detail;
- }
- _clearSearch() {
- this.state.searchWord = '';
- }
- _updateProductList(event) {
- this.render(true);
- this.trigger('switch-category', 0);
- }
+class ProductsWidget extends PosComponent {
+ /**
+ * @param {Object} props
+ * @param {number?} props.startCategoryId
+ */
+ setup() {
+ super.setup();
+ useListener("switch-category", this._switchCategory);
+ useListener("update-search", this._updateSearch);
+ useListener("clear-search", this._clearSearch);
+ useListener("update-product-list", this._updateProductList);
+ this.state = useState({ searchWord: "" });
+ onWillUnmount(this.onWillUnmount);
}
- ProductsWidget.template = 'ProductsWidget';
+ onWillUnmount() {
+ this.trigger("toggle-mobile-searchbar", false);
+ }
+ get selectedCategoryId() {
+ return this.env.pos.selectedCategoryId;
+ }
+ get searchWord() {
+ return this.state.searchWord.trim();
+ }
+ get productsToDisplay() {
+ let list = [];
+ if (this.searchWord !== "") {
+ list = this.env.pos.db.search_product_in_category(
+ this.selectedCategoryId,
+ this.searchWord
+ );
+ } else {
+ list = this.env.pos.db.get_product_by_category(this.selectedCategoryId);
+ }
+ return list.sort(function (a, b) {
+ return a.display_name.localeCompare(b.display_name);
+ });
+ }
+ get subcategories() {
+ return this.env.pos.db
+ .get_category_childs_ids(this.selectedCategoryId)
+ .map((id) => this.env.pos.db.get_category_by_id(id));
+ }
+ get breadcrumbs() {
+ if (this.selectedCategoryId === this.env.pos.db.root_category_id) {
+ return [];
+ }
+ return [
+ ...this.env.pos.db.get_category_ancestors_ids(this.selectedCategoryId).slice(1),
+ this.selectedCategoryId,
+ ].map((id) => this.env.pos.db.get_category_by_id(id));
+ }
+ get hasNoCategories() {
+ return this.env.pos.db.get_category_childs_ids(0).length === 0;
+ }
+ get shouldShowButton() {
+ return (
+ this.productsToDisplay.length === 0 &&
+ this.searchWord &&
+ !this.env.pos.isEveryProductLoaded
+ );
+ }
+ _switchCategory(event) {
+ this.env.pos.setSelectedCategoryId(event.detail);
+ }
+ _updateSearch(event) {
+ this.state.searchWord = event.detail;
+ }
+ _clearSearch() {
+ this.state.searchWord = "";
+ }
+ _updateProductList(event) {
+ this.render(true);
+ this.trigger("switch-category", 0);
+ }
+}
+ProductsWidget.template = "ProductsWidget";
- Registries.Component.add(ProductsWidget);
+Registries.Component.add(ProductsWidget);
- return ProductsWidget;
-});
+export default ProductsWidget;
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js
index 41d4f813312..07f248bd413 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js
@@ -1,99 +1,124 @@
-odoo.define('point_of_sale.ProductsWidgetControlPanel', function(require) {
- 'use strict';
+/** @odoo-module */
- const { identifyError } = require('point_of_sale.utils');
- 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");
+import { identifyError } from "@point_of_sale/js/utils";
+import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/rpc_service";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
+import { debounce } from "@web/core/utils/timing";
- const { onMounted, onWillUnmount } = owl;
+const { onMounted, onWillUnmount } = owl;
- class ProductsWidgetControlPanel extends PosComponent {
- setup() {
- super.setup();
- this.updateSearch = debounce(this.updateSearch, 100);
- this.state = { searchInput: '' };
+class ProductsWidgetControlPanel extends PosComponent {
+ setup() {
+ super.setup();
+ this.updateSearch = debounce(this.updateSearch, 100);
+ this.state = { searchInput: "" };
- onMounted(() => {
- this.env.posbus.on('search-product-from-info-popup', this, this.searchProductFromInfo)
- if(!this.env.pos.config.limited_products_loading)
- this.env.pos.isEveryProductLoaded = true;
- });
-
- onWillUnmount(() => {
- this.env.posbus.off('search-product-from-info-popup', this);
- });
- }
- _clearSearch() {
- this.state.searchInput = '';
- this.trigger('clear-search');
- }
- get displayCategImages() {
- return Object.values(this.env.pos.db.category_by_id).some(categ => categ.has_image) && !this.env.isMobile;
- }
- updateSearch(event) {
- this.trigger('update-search', this.state.searchInput);
- if (event.key === 'Enter') {
- this._onPressEnterKey()
+ onMounted(() => {
+ this.env.posbus.on("search-product-from-info-popup", this, this.searchProductFromInfo);
+ if (!this.env.pos.config.limited_products_loading) {
+ this.env.pos.isEveryProductLoaded = true;
}
- }
- async _onPressEnterKey() {
- if (!this.state.searchInput) return;
- if (!this.env.pos.isEveryProductLoaded) {
- const result = await this.loadProductFromDB();
- this.showNotification(
- _.str.sprintf(this.env._t('%s product(s) found for "%s".'),
- result.length,
- this.state.searchInput)
- , 3000);
- if (!result.length) this._clearSearch();
- }
- }
- searchProductFromInfo(productName) {
- this.state.searchInput = productName;
- this.trigger('switch-category', 0);
- this.trigger('update-search', productName);
- }
- _toggleMobileSearchbar() {
- this.trigger('toggle-mobile-searchbar');
- }
- async loadProductFromDB() {
- if(!this.state.searchInput)
- return;
+ });
- try {
- let ProductIds = await this.rpc({
- model: 'product.product',
- method: 'search',
- args: [['&',['available_in_pos', '=', true], '|','|','|',
- ['name', 'ilike', this.state.searchInput],
- ['default_code', 'ilike', this.state.searchInput],
- ['barcode', 'ilike', this.state.searchInput]]],
- context: this.env.session.user_context,
- });
- if(ProductIds.length) {
- if (!this.env.pos.isEveryProductLoaded) await this.env.pos.updateIsEveryProductLoaded();
- await this.env.pos._addProducts(ProductIds, false);
- }
- this.trigger('update-product-list');
- return ProductIds;
- } catch (error) {
- const identifiedError = identifyError(error)
- if (identifiedError instanceof ConnectionLostError || identifiedError instanceof ConnectionAbortedError) {
- return this.showPopup('OfflineErrorPopup', {
- title: this.env._t('Network Error'),
- body: this.env._t("Product is not loaded. Tried loading the product from the server but there is a network error."),
- });
- } else {
- throw error;
- }
+ onWillUnmount(() => {
+ this.env.posbus.off("search-product-from-info-popup", this);
+ });
+ }
+ _clearSearch() {
+ this.state.searchInput = "";
+ this.trigger("clear-search");
+ }
+ get displayCategImages() {
+ return (
+ Object.values(this.env.pos.db.category_by_id).some((categ) => categ.has_image) &&
+ !this.env.isMobile
+ );
+ }
+ updateSearch(event) {
+ this.trigger("update-search", this.state.searchInput);
+ if (event.key === "Enter") {
+ this._onPressEnterKey();
+ }
+ }
+ async _onPressEnterKey() {
+ if (!this.state.searchInput) {
+ return;
+ }
+ if (!this.env.pos.isEveryProductLoaded) {
+ const result = await this.loadProductFromDB();
+ this.showNotification(
+ _.str.sprintf(
+ this.env._t('%s product(s) found for "%s".'),
+ result.length,
+ this.state.searchInput
+ ),
+ 3000
+ );
+ if (!result.length) {
+ this._clearSearch();
}
}
}
- ProductsWidgetControlPanel.template = 'ProductsWidgetControlPanel';
+ searchProductFromInfo(productName) {
+ this.state.searchInput = productName;
+ this.trigger("switch-category", 0);
+ this.trigger("update-search", productName);
+ }
+ _toggleMobileSearchbar() {
+ this.trigger("toggle-mobile-searchbar");
+ }
+ async loadProductFromDB() {
+ if (!this.state.searchInput) {
+ return;
+ }
- Registries.Component.add(ProductsWidgetControlPanel);
+ try {
+ const ProductIds = await this.rpc({
+ model: "product.product",
+ method: "search",
+ args: [
+ [
+ "&",
+ ["available_in_pos", "=", true],
+ "|",
+ "|",
+ "|",
+ ["name", "ilike", this.state.searchInput],
+ ["default_code", "ilike", this.state.searchInput],
+ ["barcode", "ilike", this.state.searchInput],
+ ],
+ ],
+ context: this.env.session.user_context,
+ });
+ if (ProductIds.length) {
+ if (!this.env.pos.isEveryProductLoaded) {
+ await this.env.pos.updateIsEveryProductLoaded();
+ }
+ await this.env.pos._addProducts(ProductIds, false);
+ }
+ this.trigger("update-product-list");
+ return ProductIds;
+ } catch (error) {
+ const identifiedError = identifyError(error);
+ if (
+ identifiedError instanceof ConnectionLostError ||
+ identifiedError instanceof ConnectionAbortedError
+ ) {
+ return this.showPopup("OfflineErrorPopup", {
+ title: this.env._t("Network Error"),
+ body: this.env._t(
+ "Product is not loaded. Tried loading the product from the server but there is a network error."
+ ),
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+}
+ProductsWidgetControlPanel.template = "ProductsWidgetControlPanel";
- return ProductsWidgetControlPanel;
-});
+Registries.Component.add(ProductsWidgetControlPanel);
+
+export default ProductsWidgetControlPanel;
diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js
index 8b358a5bb6c..4f7b5e29f55 100644
--- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js
+++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js
@@ -1,50 +1,45 @@
-odoo.define('point_of_sale.OrderReceipt', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- const { onWillUpdateProps } = owl;
+const { onWillUpdateProps } = owl;
- class OrderReceipt extends PosComponent {
- setup() {
- super.setup();
- this._receiptEnv = this.props.order.getOrderReceiptEnv();
+class OrderReceipt extends PosComponent {
+ setup() {
+ super.setup();
+ this._receiptEnv = this.props.order.getOrderReceiptEnv();
- onWillUpdateProps((nextProps) => {
- this._receiptEnv = nextProps.order.getOrderReceiptEnv();
- });
- }
- get receipt() {
- return this.receiptEnv.receipt;
- }
- get orderlines() {
- return this.receiptEnv.orderlines;
- }
- get paymentlines() {
- return this.receiptEnv.paymentlines;
- }
- get isTaxIncluded() {
- return Math.abs(this.receipt.subtotal - this.receipt.total_with_tax) <= 0.000001;
- }
- get receiptEnv () {
- return this._receiptEnv;
- }
- isSimple(line) {
- return (
- line.discount === 0 &&
- line.is_in_unit &&
- line.quantity === 1 &&
- !(
- line.display_discount_policy == 'without_discount' &&
- line.price < line.price_lst
- )
- );
- }
+ onWillUpdateProps((nextProps) => {
+ this._receiptEnv = nextProps.order.getOrderReceiptEnv();
+ });
}
- OrderReceipt.template = 'OrderReceipt';
+ get receipt() {
+ return this.receiptEnv.receipt;
+ }
+ get orderlines() {
+ return this.receiptEnv.orderlines;
+ }
+ get paymentlines() {
+ return this.receiptEnv.paymentlines;
+ }
+ get isTaxIncluded() {
+ return Math.abs(this.receipt.subtotal - this.receipt.total_with_tax) <= 0.000001;
+ }
+ get receiptEnv() {
+ return this._receiptEnv;
+ }
+ isSimple(line) {
+ return (
+ line.discount === 0 &&
+ line.is_in_unit &&
+ line.quantity === 1 &&
+ !(line.display_discount_policy == "without_discount" && line.price < line.price_lst)
+ );
+ }
+}
+OrderReceipt.template = "OrderReceipt";
- Registries.Component.add(OrderReceipt);
+Registries.Component.add(OrderReceipt);
- return OrderReceipt;
-});
+export default OrderReceipt;
diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js
index cb255fa7944..5d4b5407441 100644
--- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js
@@ -1,139 +1,152 @@
-odoo.define('point_of_sale.ReceiptScreen', function (require) {
- 'use strict';
+/** @odoo-module */
- const { Printer } = require('point_of_sale.Printer');
- const { is_email } = require('web.utils');
- const { useErrorHandlers } = require('point_of_sale.custom_hooks');
- const Registries = require('point_of_sale.Registries');
- const AbstractReceiptScreen = require('point_of_sale.AbstractReceiptScreen');
+import { Printer } from "@point_of_sale/js/printers";
+import { is_email } from "web.utils";
+import { useErrorHandlers } from "@point_of_sale/js/custom_hooks";
+import Registries from "@point_of_sale/js/Registries";
+import AbstractReceiptScreen from "@point_of_sale/js/Misc/AbstractReceiptScreen";
- const { onMounted, useRef, status } = owl;
+const { onMounted, useRef, status } = owl;
- const ReceiptScreen = (AbstractReceiptScreen) => {
- class ReceiptScreen extends AbstractReceiptScreen {
- setup() {
- super.setup();
- useErrorHandlers();
- this.orderReceipt = useRef('order-receipt');
- const order = this.currentOrder;
- const partner = order.get_partner();
- this.orderUiState = order.uiState.ReceiptScreen;
- this.orderUiState.inputEmail = this.orderUiState.inputEmail || (partner && partner.email) || '';
- this.is_email = is_email;
+const ReceiptScreen = (AbstractReceiptScreen) => {
+ class ReceiptScreen extends AbstractReceiptScreen {
+ setup() {
+ super.setup();
+ useErrorHandlers();
+ this.orderReceipt = useRef("order-receipt");
+ const order = this.currentOrder;
+ const partner = order.get_partner();
+ this.orderUiState = order.uiState.ReceiptScreen;
+ this.orderUiState.inputEmail =
+ this.orderUiState.inputEmail || (partner && partner.email) || "";
+ this.is_email = is_email;
- 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();
+ 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") {
+ const images = this.orderReceipt.el.getElementsByTagName("img");
+ for (const image of images) {
+ await image.decode();
}
- }, 0);
- });
- }
- _addNewOrder() {
- this.env.pos.add_new_order();
- }
- async onSendEmail() {
- if (!is_email(this.orderUiState.inputEmail)) {
- this.orderUiState.emailSuccessful = false;
- this.orderUiState.emailNotice = this.env._t('Invalid email.');
- return;
- }
- try {
- await this._sendReceiptToCustomer();
- this.orderUiState.emailSuccessful = true;
- this.orderUiState.emailNotice = this.env._t('Email sent.');
- } catch {
- this.orderUiState.emailSuccessful = false;
- this.orderUiState.emailNotice = this.env._t('Sending email failed. Please try again.');
- }
- }
- get orderAmountPlusTip() {
- const order = this.currentOrder;
- const orderTotalAmount = order.get_total_with_tax();
- const tip_product_id = this.env.pos.config.tip_product_id && this.env.pos.config.tip_product_id[0];
- const tipLine = order
- .get_orderlines()
- .find((line) => tip_product_id && line.product.id === tip_product_id);
- const tipAmount = tipLine ? tipLine.get_all_prices().priceWithTax : 0;
- const orderAmountStr = this.env.pos.format_currency(orderTotalAmount - tipAmount);
- if (!tipAmount) return orderAmountStr;
- const tipAmountStr = this.env.pos.format_currency(tipAmount);
- return `${orderAmountStr} + ${tipAmountStr} tip`;
- }
- get currentOrder() {
- return this.env.pos.get_order();
- }
- get nextScreen() {
- return { name: 'ProductScreen' };
- }
- whenClosing() {
- this.orderDone();
- }
- /**
- * This function is called outside the rendering call stack. This way,
- * we don't block the displaying of ReceiptScreen when it is mounted; additionally,
- * any error that can happen during the printing does not affect the rendering.
- */
- async handleAutoPrint() {
- if (this._shouldAutoPrint()) {
- await this.printReceipt();
- if (this.currentOrder._printed && this._shouldCloseImmediately()) {
- this.whenClosing();
+ await this.handleAutoPrint();
}
- }
+ }, 0);
+ });
+ }
+ _addNewOrder() {
+ this.env.pos.add_new_order();
+ }
+ async onSendEmail() {
+ if (!is_email(this.orderUiState.inputEmail)) {
+ this.orderUiState.emailSuccessful = false;
+ this.orderUiState.emailNotice = this.env._t("Invalid email.");
+ return;
}
- orderDone() {
- this.env.pos.removeOrder(this.currentOrder);
- this._addNewOrder();
- const { name, props } = this.nextScreen;
- this.showScreen(name, props);
- if (this.env.pos.config.iface_customer_facing_display) {
- this.env.pos.send_current_order_to_customer_facing_display();
- }
- }
- async printReceipt() {
- const isPrinted = await this._printReceipt();
- if (isPrinted) {
- this.currentOrder._printed = true;
- }
- }
- _shouldAutoPrint() {
- return this.env.pos.config.iface_print_auto && !this.currentOrder._printed;
- }
- _shouldCloseImmediately() {
- var invoiced_finalized = this.currentOrder.is_to_invoice() ? this.currentOrder.finalized : true;
- return this.env.proxy.printer && this.env.pos.config.iface_print_skip_screen && invoiced_finalized;
- }
- async _sendReceiptToCustomer() {
- const printer = new Printer(null, this.env.pos);
- const receiptString = this.orderReceipt.el.innerHTML;
- const ticketImage = await printer.htmlToImg(receiptString);
- const order = this.currentOrder;
- const partner = order.get_partner();
- const orderName = order.get_name();
- const orderPartner = { email: this.orderUiState.inputEmail, name: partner ? partner.name : this.orderUiState.inputEmail };
- const order_server_id = this.env.pos.validated_orders_name_server_id_map[orderName];
- await this.rpc({
- model: 'pos.order',
- method: 'action_receipt_to_customer',
- args: [[order_server_id], orderName, orderPartner, ticketImage],
- });
+ try {
+ await this._sendReceiptToCustomer();
+ this.orderUiState.emailSuccessful = true;
+ this.orderUiState.emailNotice = this.env._t("Email sent.");
+ } catch {
+ this.orderUiState.emailSuccessful = false;
+ this.orderUiState.emailNotice = this.env._t(
+ "Sending email failed. Please try again."
+ );
}
}
- ReceiptScreen.template = 'ReceiptScreen';
- return ReceiptScreen;
- };
-
- Registries.Component.addByExtending(ReceiptScreen, AbstractReceiptScreen);
-
+ get orderAmountPlusTip() {
+ const order = this.currentOrder;
+ const orderTotalAmount = order.get_total_with_tax();
+ const tip_product_id =
+ this.env.pos.config.tip_product_id && this.env.pos.config.tip_product_id[0];
+ const tipLine = order
+ .get_orderlines()
+ .find((line) => tip_product_id && line.product.id === tip_product_id);
+ const tipAmount = tipLine ? tipLine.get_all_prices().priceWithTax : 0;
+ const orderAmountStr = this.env.pos.format_currency(orderTotalAmount - tipAmount);
+ if (!tipAmount) {
+ return orderAmountStr;
+ }
+ const tipAmountStr = this.env.pos.format_currency(tipAmount);
+ return `${orderAmountStr} + ${tipAmountStr} tip`;
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ get nextScreen() {
+ return { name: "ProductScreen" };
+ }
+ whenClosing() {
+ this.orderDone();
+ }
+ /**
+ * This function is called outside the rendering call stack. This way,
+ * we don't block the displaying of ReceiptScreen when it is mounted; additionally,
+ * any error that can happen during the printing does not affect the rendering.
+ */
+ async handleAutoPrint() {
+ if (this._shouldAutoPrint()) {
+ await this.printReceipt();
+ if (this.currentOrder._printed && this._shouldCloseImmediately()) {
+ this.whenClosing();
+ }
+ }
+ }
+ orderDone() {
+ this.env.pos.removeOrder(this.currentOrder);
+ this._addNewOrder();
+ const { name, props } = this.nextScreen;
+ this.showScreen(name, props);
+ if (this.env.pos.config.iface_customer_facing_display) {
+ this.env.pos.send_current_order_to_customer_facing_display();
+ }
+ }
+ async printReceipt() {
+ const isPrinted = await this._printReceipt();
+ if (isPrinted) {
+ this.currentOrder._printed = true;
+ }
+ }
+ _shouldAutoPrint() {
+ return this.env.pos.config.iface_print_auto && !this.currentOrder._printed;
+ }
+ _shouldCloseImmediately() {
+ var invoiced_finalized = this.currentOrder.is_to_invoice()
+ ? this.currentOrder.finalized
+ : true;
+ return (
+ this.env.proxy.printer &&
+ this.env.pos.config.iface_print_skip_screen &&
+ invoiced_finalized
+ );
+ }
+ async _sendReceiptToCustomer() {
+ const printer = new Printer(null, this.env.pos);
+ const receiptString = this.orderReceipt.el.innerHTML;
+ const ticketImage = await printer.htmlToImg(receiptString);
+ const order = this.currentOrder;
+ const partner = order.get_partner();
+ const orderName = order.get_name();
+ const orderPartner = {
+ email: this.orderUiState.inputEmail,
+ name: partner ? partner.name : this.orderUiState.inputEmail,
+ };
+ const order_server_id = this.env.pos.validated_orders_name_server_id_map[orderName];
+ await this.rpc({
+ model: "pos.order",
+ method: "action_receipt_to_customer",
+ args: [[order_server_id], orderName, orderPartner, ticketImage],
+ });
+ }
+ }
+ ReceiptScreen.template = "ReceiptScreen";
return ReceiptScreen;
-});
+};
+
+Registries.Component.addByExtending(ReceiptScreen, AbstractReceiptScreen);
+
+export default ReceiptScreen;
diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js
index f630ac607e4..282954aefd0 100644
--- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js
+++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js
@@ -1,13 +1,11 @@
-odoo.define('point_of_sale.WrappedProductNameLines', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class WrappedProductNameLines extends PosComponent {}
- WrappedProductNameLines.template = 'WrappedProductNameLines';
+class WrappedProductNameLines extends PosComponent {}
+WrappedProductNameLines.template = "WrappedProductNameLines";
- Registries.Component.add(WrappedProductNameLines);
+Registries.Component.add(WrappedProductNameLines);
- return WrappedProductNameLines;
-});
+export default WrappedProductNameLines;
diff --git a/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js b/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js
index 172574a6c42..78b57cb11f4 100644
--- a/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js
@@ -1,105 +1,102 @@
-odoo.define('point_of_sale.ScaleScreen', function(require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const { round_precision: round_pr } = require('web.utils');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import { round_precision as round_pr } from "web.utils";
+import Registries from "@point_of_sale/js/Registries";
- const { onMounted, onWillUnmount, useExternalListener, useState } = owl;
+const { onMounted, onWillUnmount, useExternalListener, useState } = owl;
- class ScaleScreen extends PosComponent {
- /**
- * @param {Object} props
- * @param {Object} props.product The product to weight.
- */
- setup() {
- super.setup();
- useExternalListener(document, 'keyup', this._onHotkeys);
- this.state = useState({ weight: 0 });
- onMounted(this.onMounted);
- onWillUnmount(this.onWillUnmount);
- }
- onMounted() {
- // start the scale reading
- this._readScale();
- }
- onWillUnmount() {
- // stop the scale reading
- this.env.proxy_queue.clear();
- }
- back() {
- this.props.resolve({ confirmed: false, payload: null });
- this.trigger('close-temp-screen');
- }
- confirm() {
- this.props.resolve({
- confirmed: true,
- payload: { weight: this.state.weight },
- });
- this.trigger('close-temp-screen');
- }
- _onHotkeys(event) {
- if (event.key === 'Escape') {
- this.back();
- } else if (event.key === 'Enter') {
- this.confirm();
- }
- }
- _readScale() {
- this.env.proxy_queue.schedule(this._setWeight.bind(this), {
- duration: 500,
- repeat: true,
- });
- }
- async _setWeight() {
- const reading = await this.env.proxy.scale_read();
- this.state.weight = reading.weight;
- }
- get _activePricelist() {
- const current_order = this.env.pos.get_order();
- let current_pricelist = this.env.pos.default_pricelist;
- if (current_order) {
- current_pricelist = current_order.pricelist;
- }
- return current_pricelist;
- }
- get productWeightString() {
- const defaultstr = (this.state.weight || 0).toFixed(3) + ' Kg';
- if (!this.props.product || !this.env.pos) {
- return defaultstr;
- }
- const unit_id = this.props.product.uom_id;
- if (!unit_id) {
- return defaultstr;
- }
- const unit = this.env.pos.units_by_id[unit_id[0]];
- const weight = round_pr(this.state.weight || 0, unit.rounding);
- let weightstr = weight.toFixed(Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10)));
- weightstr += ' ' + unit.name;
- return weightstr;
- }
- get computedPriceString() {
- return this.env.pos.format_currency(this.productPrice * this.state.weight);
- }
- get productPrice() {
- const product = this.props.product;
- return (product ? product.get_price(this._activePricelist, this.state.weight) : 0) || 0;
- }
- get productName() {
- return (
- (this.props.product ? this.props.product.display_name : undefined) ||
- 'Unnamed Product'
- );
- }
- get productUom() {
- return this.props.product
- ? this.env.pos.units_by_id[this.props.product.uom_id[0]].name
- : '';
+class ScaleScreen extends PosComponent {
+ /**
+ * @param {Object} props
+ * @param {Object} props.product The product to weight.
+ */
+ setup() {
+ super.setup();
+ useExternalListener(document, "keyup", this._onHotkeys);
+ this.state = useState({ weight: 0 });
+ onMounted(this.onMounted);
+ onWillUnmount(this.onWillUnmount);
+ }
+ onMounted() {
+ // start the scale reading
+ this._readScale();
+ }
+ onWillUnmount() {
+ // stop the scale reading
+ this.env.proxy_queue.clear();
+ }
+ back() {
+ this.props.resolve({ confirmed: false, payload: null });
+ this.trigger("close-temp-screen");
+ }
+ confirm() {
+ this.props.resolve({
+ confirmed: true,
+ payload: { weight: this.state.weight },
+ });
+ this.trigger("close-temp-screen");
+ }
+ _onHotkeys(event) {
+ if (event.key === "Escape") {
+ this.back();
+ } else if (event.key === "Enter") {
+ this.confirm();
}
}
- ScaleScreen.template = 'ScaleScreen';
+ _readScale() {
+ this.env.proxy_queue.schedule(this._setWeight.bind(this), {
+ duration: 500,
+ repeat: true,
+ });
+ }
+ async _setWeight() {
+ const reading = await this.env.proxy.scale_read();
+ this.state.weight = reading.weight;
+ }
+ get _activePricelist() {
+ const current_order = this.env.pos.get_order();
+ let current_pricelist = this.env.pos.default_pricelist;
+ if (current_order) {
+ current_pricelist = current_order.pricelist;
+ }
+ return current_pricelist;
+ }
+ get productWeightString() {
+ const defaultstr = (this.state.weight || 0).toFixed(3) + " Kg";
+ if (!this.props.product || !this.env.pos) {
+ return defaultstr;
+ }
+ const unit_id = this.props.product.uom_id;
+ if (!unit_id) {
+ return defaultstr;
+ }
+ const unit = this.env.pos.units_by_id[unit_id[0]];
+ const weight = round_pr(this.state.weight || 0, unit.rounding);
+ let weightstr = weight.toFixed(Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10)));
+ weightstr += " " + unit.name;
+ return weightstr;
+ }
+ get computedPriceString() {
+ return this.env.pos.format_currency(this.productPrice * this.state.weight);
+ }
+ get productPrice() {
+ const product = this.props.product;
+ return (product ? product.get_price(this._activePricelist, this.state.weight) : 0) || 0;
+ }
+ get productName() {
+ return (
+ (this.props.product ? this.props.product.display_name : undefined) || "Unnamed Product"
+ );
+ }
+ get productUom() {
+ return this.props.product
+ ? this.env.pos.units_by_id[this.props.product.uom_id[0]].name
+ : "";
+ }
+}
+ScaleScreen.template = "ScaleScreen";
- Registries.Component.add(ScaleScreen);
+Registries.Component.add(ScaleScreen);
- return ScaleScreen;
-});
+export default ScaleScreen;
diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js
index a13925a51fb..fd6c8005e6b 100644
--- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js
@@ -1,128 +1,131 @@
-odoo.define('point_of_sale.InvoiceButton', function (require) {
- 'use strict';
+/** @odoo-module */
- 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');
+import { useListener } from "@web/core/utils/hooks";
+import { isConnectionError } from "@point_of_sale/js/utils";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class InvoiceButton extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this._onClick);
+class InvoiceButton extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this._onClick);
+ }
+ get isAlreadyInvoiced() {
+ if (!this.props.order) {
+ return false;
}
- get isAlreadyInvoiced() {
- if (!this.props.order) return false;
- return Boolean(this.props.order.account_move);
+ return Boolean(this.props.order.account_move);
+ }
+ get commandName() {
+ if (!this.props.order) {
+ return this.env._t("Invoice");
+ } else {
+ return this.isAlreadyInvoiced ? this.env._t("Reprint Invoice") : this.env._t("Invoice");
}
- get commandName() {
- if (!this.props.order) {
- return this.env._t('Invoice');
+ }
+ async _downloadInvoice(orderId) {
+ try {
+ const [orderWithInvoice] = await this.rpc({
+ method: "read",
+ model: "pos.order",
+ args: [orderId, ["account_move"]],
+ kwargs: { load: false },
+ });
+ if (orderWithInvoice && orderWithInvoice.account_move) {
+ await this.env.legacyActionManager.do_action("account.account_invoices", {
+ additional_context: {
+ active_ids: [orderWithInvoice.account_move],
+ },
+ });
+ }
+ } catch (error) {
+ if (error instanceof Error) {
+ throw error;
} else {
- return this.isAlreadyInvoiced
- ? this.env._t('Reprint Invoice')
- : this.env._t('Invoice');
- }
- }
- async _downloadInvoice(orderId) {
- try {
- const [orderWithInvoice] = await this.rpc({
- method: 'read',
- model: 'pos.order',
- args: [orderId, ['account_move']],
- kwargs: { load: false },
+ // NOTE: error here is most probably undefined
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Network Error"),
+ body: this.env._t("Unable to download invoice."),
});
- if (orderWithInvoice && orderWithInvoice.account_move) {
- await this.env.legacyActionManager.do_action('account.account_invoices', {
- additional_context: {
- active_ids: [orderWithInvoice.account_move],
- },
- });
- }
- } catch (error) {
- if (error instanceof Error) {
- throw error;
- } else {
- // NOTE: error here is most probably undefined
- this.showPopup('ErrorPopup', {
- title: this.env._t('Network Error'),
- body: this.env._t('Unable to download invoice.'),
- });
- }
- }
- }
- async _invoiceOrder() {
- const order = this.props.order;
- if (!order) return;
-
- const orderId = order.backendId;
-
- // Part 0. If already invoiced, print the invoice.
- if (this.isAlreadyInvoiced) {
- await this._downloadInvoice(orderId);
- return;
- }
-
- // Part 1: Handle missing partner.
- // Write to pos.order the selected partner.
- if (!order.get_partner()) {
- const { confirmed: confirmedPopup } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Need customer to invoice'),
- body: this.env._t('Do you want to open the customer list to select customer?'),
- });
- if (!confirmedPopup) return;
-
- const { confirmed: confirmedTempScreen, payload: newPartner } = await this.showTempScreen(
- 'PartnerListScreen'
- );
- if (!confirmedTempScreen) return;
-
- await this.rpc({
- model: 'pos.order',
- method: 'write',
- args: [[orderId], { partner_id: newPartner.id }],
- kwargs: { context: this.env.session.user_context },
- });
- }
-
- // Part 2: Invoice the order.
- await this.rpc(
- {
- model: 'pos.order',
- method: 'action_pos_order_invoice',
- args: [orderId],
- kwargs: { context: this.env.session.user_context },
- },
- {
- timeout: 30000,
- shadow: true,
- }
- );
-
- // Part 3: Download invoice.
- await this._downloadInvoice(orderId);
- this.trigger('order-invoiced', orderId);
- }
- async _onClick() {
- try {
- this.el.style.pointerEvents = 'none';
- await this._invoiceOrder();
- } catch (error) {
- if (isConnectionError(error)) {
- this.showPopup('ErrorPopup', {
- title: this.env._t('Network Error'),
- body: this.env._t('Unable to invoice order.'),
- });
- } else {
- throw error;
- }
- } finally {
- this.el.style.pointerEvents = 'auto';
}
}
}
- InvoiceButton.template = 'InvoiceButton';
- Registries.Component.add(InvoiceButton);
+ async _invoiceOrder() {
+ const order = this.props.order;
+ if (!order) {
+ return;
+ }
- return InvoiceButton;
-});
+ const orderId = order.backendId;
+
+ // Part 0. If already invoiced, print the invoice.
+ if (this.isAlreadyInvoiced) {
+ await this._downloadInvoice(orderId);
+ return;
+ }
+
+ // Part 1: Handle missing partner.
+ // Write to pos.order the selected partner.
+ if (!order.get_partner()) {
+ const { confirmed: confirmedPopup } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Need customer to invoice"),
+ body: this.env._t("Do you want to open the customer list to select customer?"),
+ });
+ if (!confirmedPopup) {
+ return;
+ }
+
+ const { confirmed: confirmedTempScreen, payload: newPartner } =
+ await this.showTempScreen("PartnerListScreen");
+ if (!confirmedTempScreen) {
+ return;
+ }
+
+ await this.rpc({
+ model: "pos.order",
+ method: "write",
+ args: [[orderId], { partner_id: newPartner.id }],
+ kwargs: { context: this.env.session.user_context },
+ });
+ }
+
+ // Part 2: Invoice the order.
+ await this.rpc(
+ {
+ model: "pos.order",
+ method: "action_pos_order_invoice",
+ args: [orderId],
+ kwargs: { context: this.env.session.user_context },
+ },
+ {
+ timeout: 30000,
+ shadow: true,
+ }
+ );
+
+ // Part 3: Download invoice.
+ await this._downloadInvoice(orderId);
+ this.trigger("order-invoiced", orderId);
+ }
+ async _onClick() {
+ try {
+ this.el.style.pointerEvents = "none";
+ await this._invoiceOrder();
+ } catch (error) {
+ if (isConnectionError(error)) {
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Network Error"),
+ body: this.env._t("Unable to invoice order."),
+ });
+ } else {
+ throw error;
+ }
+ } finally {
+ this.el.style.pointerEvents = "auto";
+ }
+ }
+}
+InvoiceButton.template = "InvoiceButton";
+Registries.Component.add(InvoiceButton);
+
+export default InvoiceButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js
index 1306f51494c..6eb5d8bc97c 100644
--- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js
@@ -1,22 +1,22 @@
-odoo.define('point_of_sale.ReprintReceiptButton', function (require) {
- 'use strict';
+/** @odoo-module */
- const { useListener } = require("@web/core/utils/hooks");
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import { useListener } from "@web/core/utils/hooks";
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- class ReprintReceiptButton extends PosComponent {
- setup() {
- super.setup();
- useListener('click', this._onClick);
- }
- async _onClick() {
- if (!this.props.order) return;
- this.showScreen('ReprintReceiptScreen', { order: this.props.order });
- }
+class ReprintReceiptButton extends PosComponent {
+ setup() {
+ super.setup();
+ useListener("click", this._onClick);
}
- ReprintReceiptButton.template = 'ReprintReceiptButton';
- Registries.Component.add(ReprintReceiptButton);
+ async _onClick() {
+ if (!this.props.order) {
+ return;
+ }
+ this.showScreen("ReprintReceiptScreen", { order: this.props.order });
+ }
+}
+ReprintReceiptButton.template = "ReprintReceiptButton";
+Registries.Component.add(ReprintReceiptButton);
- return ReprintReceiptButton;
-});
+export default ReprintReceiptButton;
diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js
index 9c60498c58b..ffabdb098c8 100644
--- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js
+++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js
@@ -1,29 +1,27 @@
-odoo.define('point_of_sale.OrderDetails', function (require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
- /**
- * @props {models.Order} order
- */
- class OrderDetails extends PosComponent {
- get order() {
- return this.props.order;
- }
- get orderlines() {
- return this.order ? this.order.orderlines : [];
- }
- get total() {
- return this.env.pos.format_currency(this.order ? this.order.get_total_with_tax() : 0);
- }
- get tax() {
- return this.env.pos.format_currency(this.order ? this.order.get_total_tax() : 0)
- }
+/**
+ * @props {models.Order} order
+ */
+class OrderDetails extends PosComponent {
+ get order() {
+ return this.props.order;
}
- OrderDetails.template = 'OrderDetails';
+ get orderlines() {
+ return this.order ? this.order.orderlines : [];
+ }
+ get total() {
+ return this.env.pos.format_currency(this.order ? this.order.get_total_with_tax() : 0);
+ }
+ get tax() {
+ return this.env.pos.format_currency(this.order ? this.order.get_total_tax() : 0);
+ }
+}
+OrderDetails.template = "OrderDetails";
- Registries.Component.add(OrderDetails);
+Registries.Component.add(OrderDetails);
- return OrderDetails;
-});
+export default OrderDetails;
diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js
index 8af3df65ab4..b4de4db9c77 100644
--- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js
+++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js
@@ -1,81 +1,79 @@
-odoo.define('point_of_sale.OrderlineDetails', function (require) {
- 'use strict';
+/** @odoo-module */
- const PosComponent = require('point_of_sale.PosComponent');
- const Registries = require('point_of_sale.Registries');
- const { format } = require('web.field_utils');
- const { round_precision: round_pr } = require('web.utils');
+import PosComponent from "@point_of_sale/js/PosComponent";
+import Registries from "@point_of_sale/js/Registries";
+import { format } from "web.field_utils";
+import { round_precision as round_pr } from "web.utils";
- /**
- * @props {pos.order.line} line
- */
- class OrderlineDetails extends PosComponent {
- get line() {
- const line = this.props.line;
- const formatQty = (line) => {
- const quantity = line.get_quantity();
- const unit = line.get_unit();
- const decimals = this.env.pos.dp['Product Unit of Measure'];
- const rounding = Math.max(unit.rounding, Math.pow(10, -decimals));
- const roundedQuantity = round_pr(quantity, rounding);
- return format.float(roundedQuantity, { digits: [69, decimals] });
- };
- return {
- productName: line.get_full_product_name(),
- totalPrice: line.get_price_with_tax(),
- quantity: formatQty(line),
- unit: line.get_unit().name,
- unitPrice: line.get_unit_price(),
- };
- }
- get productName() {
- return this.line.productName;
- }
- get totalPrice() {
- return this.env.pos.format_currency(this.line.totalPrice);
- }
- get quantity() {
- return this.line.quantity;
- }
- get unitPrice() {
- return this.env.pos.format_currency(this.line.unitPrice);
- }
- get unit() {
- return this.line.unit;
- }
- get pricePerUnit() {
- return ` ${this.unit} at ${this.unitPrice} / ${this.unit}`;
- }
- get customerNote() {
- return this.props.line.get_customer_note();
- }
- getToRefundDetail() {
- return this.env.pos.toRefundLines[this.props.line.id];
- }
- hasRefundedQty() {
- return !this.env.pos.isProductQtyZero(this.props.line.refunded_qty);
- }
- getFormattedRefundedQty() {
- return this.env.pos.formatProductQty(this.props.line.refunded_qty);
- }
- hasToRefundQty() {
- const toRefundDetail = this.getToRefundDetail();
- return !this.env.pos.isProductQtyZero(toRefundDetail && toRefundDetail.qty);
- }
- getFormattedToRefundQty() {
- const toRefundDetail = this.getToRefundDetail();
- return this.env.pos.formatProductQty(toRefundDetail && toRefundDetail.qty);
- }
- getRefundingMessage() {
- return _.str.sprintf(this.env._t('Refunding %s in '), this.getFormattedToRefundQty());
- }
- getToRefundMessage() {
- return _.str.sprintf(this.env._t('To Refund: %s'), this.getFormattedToRefundQty());
- }
+/**
+ * @props {pos.order.line} line
+ */
+class OrderlineDetails extends PosComponent {
+ get line() {
+ const line = this.props.line;
+ const formatQty = (line) => {
+ const quantity = line.get_quantity();
+ const unit = line.get_unit();
+ const decimals = this.env.pos.dp["Product Unit of Measure"];
+ const rounding = Math.max(unit.rounding, Math.pow(10, -decimals));
+ const roundedQuantity = round_pr(quantity, rounding);
+ return format.float(roundedQuantity, { digits: [69, decimals] });
+ };
+ return {
+ productName: line.get_full_product_name(),
+ totalPrice: line.get_price_with_tax(),
+ quantity: formatQty(line),
+ unit: line.get_unit().name,
+ unitPrice: line.get_unit_price(),
+ };
}
- OrderlineDetails.template = 'OrderlineDetails';
+ get productName() {
+ return this.line.productName;
+ }
+ get totalPrice() {
+ return this.env.pos.format_currency(this.line.totalPrice);
+ }
+ get quantity() {
+ return this.line.quantity;
+ }
+ get unitPrice() {
+ return this.env.pos.format_currency(this.line.unitPrice);
+ }
+ get unit() {
+ return this.line.unit;
+ }
+ get pricePerUnit() {
+ return ` ${this.unit} at ${this.unitPrice} / ${this.unit}`;
+ }
+ get customerNote() {
+ return this.props.line.get_customer_note();
+ }
+ getToRefundDetail() {
+ return this.env.pos.toRefundLines[this.props.line.id];
+ }
+ hasRefundedQty() {
+ return !this.env.pos.isProductQtyZero(this.props.line.refunded_qty);
+ }
+ getFormattedRefundedQty() {
+ return this.env.pos.formatProductQty(this.props.line.refunded_qty);
+ }
+ hasToRefundQty() {
+ const toRefundDetail = this.getToRefundDetail();
+ return !this.env.pos.isProductQtyZero(toRefundDetail && toRefundDetail.qty);
+ }
+ getFormattedToRefundQty() {
+ const toRefundDetail = this.getToRefundDetail();
+ return this.env.pos.formatProductQty(toRefundDetail && toRefundDetail.qty);
+ }
+ getRefundingMessage() {
+ return _.str.sprintf(this.env._t("Refunding %s in "), this.getFormattedToRefundQty());
+ }
+ getToRefundMessage() {
+ return _.str.sprintf(this.env._t("To Refund: %s"), this.getFormattedToRefundQty());
+ }
+}
+OrderlineDetails.template = "OrderlineDetails";
- Registries.Component.add(OrderlineDetails);
+Registries.Component.add(OrderlineDetails);
- return OrderlineDetails;
-});
+export default OrderlineDetails;
diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js
index f860ba47150..22b5050b08d 100644
--- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ReprintReceiptScreen.js
@@ -1,36 +1,35 @@
-odoo.define('point_of_sale.ReprintReceiptScreen', function (require) {
- 'use strict';
+/** @odoo-module */
- const AbstractReceiptScreen = require('point_of_sale.AbstractReceiptScreen');
- const Registries = require('point_of_sale.Registries');
+import AbstractReceiptScreen from "@point_of_sale/js/Misc/AbstractReceiptScreen";
+import Registries from "@point_of_sale/js/Registries";
- const ReprintReceiptScreen = (AbstractReceiptScreen) => {
- class ReprintReceiptScreen extends AbstractReceiptScreen {
- setup() {
- super.setup();
- owl.onMounted(this.onMounted);
- }
- onMounted() {
- this.printReceipt();
- }
- confirm() {
- this.showScreen('TicketScreen', { reuseSavedUIState: true });
- }
- async printReceipt() {
- if(this.env.proxy.printer && this.env.pos.config.iface_print_skip_screen) {
- let result = await this._printReceipt();
- if(result)
- this.showScreen('TicketScreen', { reuseSavedUIState: true });
+const ReprintReceiptScreen = (AbstractReceiptScreen) => {
+ class ReprintReceiptScreen extends AbstractReceiptScreen {
+ setup() {
+ super.setup();
+ owl.onMounted(this.onMounted);
+ }
+ onMounted() {
+ this.printReceipt();
+ }
+ confirm() {
+ this.showScreen("TicketScreen", { reuseSavedUIState: true });
+ }
+ async printReceipt() {
+ if (this.env.proxy.printer && this.env.pos.config.iface_print_skip_screen) {
+ const result = await this._printReceipt();
+ if (result) {
+ this.showScreen("TicketScreen", { reuseSavedUIState: true });
}
}
- async tryReprint() {
- await this._printReceipt();
- }
}
- ReprintReceiptScreen.template = 'ReprintReceiptScreen';
- return ReprintReceiptScreen;
- };
- Registries.Component.addByExtending(ReprintReceiptScreen, AbstractReceiptScreen);
-
+ async tryReprint() {
+ await this._printReceipt();
+ }
+ }
+ ReprintReceiptScreen.template = "ReprintReceiptScreen";
return ReprintReceiptScreen;
-});
+};
+Registries.Component.addByExtending(ReprintReceiptScreen, AbstractReceiptScreen);
+
+export default ReprintReceiptScreen;
diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js
index c1ef0c25d3d..cbec4b7221d 100644
--- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js
@@ -1,658 +1,703 @@
-odoo.define('point_of_sale.TicketScreen', function (require) {
- 'use strict';
+/** @odoo-module */
- const { Order } = require('point_of_sale.models');
- const Registries = require('point_of_sale.Registries');
- const IndependentToOrderScreen = require('point_of_sale.IndependentToOrderScreen');
- const NumberBuffer = require('point_of_sale.NumberBuffer');
- const { useListener } = require("@web/core/utils/hooks");
- const { parse } = require('web.field_utils');
+import { Order } from "@point_of_sale/js/models";
+import Registries from "@point_of_sale/js/Registries";
+import IndependentToOrderScreen from "@point_of_sale/js/Misc/IndependentToOrderScreen";
+import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
+import { useListener } from "@web/core/utils/hooks";
+import { parse } from "web.field_utils";
- const { onMounted, onWillUnmount, useState } = owl;
+const { onMounted, onWillUnmount, useState } = owl;
- class TicketScreen extends IndependentToOrderScreen {
- setup() {
- super.setup();
- useListener('close-screen', this._onCloseScreen);
- useListener('filter-selected', this._onFilterSelected);
- useListener('search', this._onSearch);
- useListener('click-order', this._onClickOrder);
- useListener('create-new-order', this._onCreateNewOrder);
- useListener('delete-order', this._onDeleteOrder);
- useListener('next-page', this._onNextPage);
- useListener('prev-page', this._onPrevPage);
- useListener('order-invoiced', this._onInvoiceOrder);
- useListener('click-order-line', this._onClickOrderline);
- useListener('click-refund-order-uid', this._onClickRefundOrderUid);
- useListener('update-selected-orderline', this._onUpdateSelectedOrderline);
- useListener('do-refund', this._onDoRefund);
- NumberBuffer.use({
- nonKeyboardInputEvent: 'numpad-click-input',
- triggerAtInput: 'update-selected-orderline',
- });
- this._state = this.env.pos.TICKET_SCREEN_STATE;
- this.state = useState({
- showSearchBar: !this.env.isMobile,
- });
- const defaultUIState = this.props.reuseSavedUIState
- ? this._state.ui
- : {
- selectedSyncedOrderId: null,
- searchDetails: this.env.pos.getDefaultSearchDetails(),
- filter: null,
- selectedOrderlineIds: {},
- };
- Object.assign(this._state.ui, defaultUIState, this.props.ui || {});
+class TicketScreen extends IndependentToOrderScreen {
+ setup() {
+ super.setup();
+ useListener("close-screen", this._onCloseScreen);
+ useListener("filter-selected", this._onFilterSelected);
+ useListener("search", this._onSearch);
+ useListener("click-order", this._onClickOrder);
+ useListener("create-new-order", this._onCreateNewOrder);
+ useListener("delete-order", this._onDeleteOrder);
+ useListener("next-page", this._onNextPage);
+ useListener("prev-page", this._onPrevPage);
+ useListener("order-invoiced", this._onInvoiceOrder);
+ useListener("click-order-line", this._onClickOrderline);
+ useListener("click-refund-order-uid", this._onClickRefundOrderUid);
+ useListener("update-selected-orderline", this._onUpdateSelectedOrderline);
+ useListener("do-refund", this._onDoRefund);
+ NumberBuffer.use({
+ nonKeyboardInputEvent: "numpad-click-input",
+ triggerAtInput: "update-selected-orderline",
+ });
+ this._state = this.env.pos.TICKET_SCREEN_STATE;
+ this.state = useState({
+ showSearchBar: !this.env.isMobile,
+ });
+ const defaultUIState = this.props.reuseSavedUIState
+ ? this._state.ui
+ : {
+ selectedSyncedOrderId: null,
+ searchDetails: this.env.pos.getDefaultSearchDetails(),
+ filter: null,
+ selectedOrderlineIds: {},
+ };
+ Object.assign(this._state.ui, defaultUIState, this.props.ui || {});
- onMounted(this.onMounted);
- onWillUnmount(this.onWillUnmount);
- }
- //#region LIFECYCLE METHODS
- 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 } });
- });
- }
- onWillUnmount() {
- this.env.posbus.off('ticket-button-clicked', this);
- }
- //#endregion
- //#region EVENT HANDLERS
- _onCloseScreen() {
- this.close();
- }
- async _onFilterSelected(event) {
- this._state.ui.filter = event.detail.filter;
- if (this._state.ui.filter == 'SYNCED') {
- await this._fetchSyncedOrders();
- }
- }
- async _onSearch(event) {
- Object.assign(this._state.ui.searchDetails, event.detail);
- if (this._state.ui.filter == 'SYNCED') {
- this._state.syncedOrders.currentPage = 1;
- await this._fetchSyncedOrders();
- }
- }
- _onClickOrder({ detail: clickedOrder }) {
- if (!clickedOrder || clickedOrder.locked) {
- if (this._state.ui.selectedSyncedOrderId == clickedOrder.backendId) {
- this._state.ui.selectedSyncedOrderId = null;
- } else {
- this._state.ui.selectedSyncedOrderId = clickedOrder.backendId;
- }
- if (!this.getSelectedOrderlineId()) {
- // Automatically select the first orderline of the selected order.
- const firstLine = clickedOrder.get_orderlines()[0];
- if (firstLine) {
- this._state.ui.selectedOrderlineIds[clickedOrder.backendId] = firstLine.id;
- }
- }
- NumberBuffer.reset();
- } else {
- this._setOrder(clickedOrder);
- }
- }
- _onCreateNewOrder() {
- this.env.pos.add_new_order();
- this.showScreen('ProductScreen');
- }
- _selectNextOrder(currentOrder) {
- const currentOrderIndex = this._getOrderList().indexOf(currentOrder);
- const orderList = this._getOrderList();
- this.env.pos.set_order(orderList[currentOrderIndex+1] || orderList[currentOrderIndex-1]);
- }
- async _onDeleteOrder({ detail: order }) {
- const screen = order.get_screen_data();
- if (['ProductScreen', 'PaymentScreen'].includes(screen.name) && order.get_orderlines().length > 0) {
- const { confirmed } = await this.showPopup('ConfirmPopup', {
- title: this.env._t('Existing orderlines'),
- body: _.str.sprintf(
- this.env._t('%s has a total amount of %s, are you sure you want to delete this order ?'),
- order.name, this.getTotal(order)
- ),
- });
- if (!confirmed) return;
- }
- if (order && (await this._onBeforeDeleteOrder(order))) {
- if (order === this.env.pos.get_order()) {
- this._selectNextOrder(order);
- }
- this.env.pos.removeOrder(order);
- }
- }
- async _onNextPage() {
- if (this._state.syncedOrders.currentPage < this._getLastPage()) {
- this._state.syncedOrders.currentPage += 1;
- await this._fetchSyncedOrders();
- }
- }
- async _onPrevPage() {
- if (this._state.syncedOrders.currentPage > 1) {
- this._state.syncedOrders.currentPage -= 1;
- await this._fetchSyncedOrders();
- }
- }
- async _onInvoiceOrder({ detail: orderId }) {
- this.env.pos._invalidateSyncedOrdersCache([orderId]);
+ onMounted(this.onMounted);
+ onWillUnmount(this.onWillUnmount);
+ }
+ //#region LIFECYCLE METHODS
+ 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 } });
+ });
+ }
+ onWillUnmount() {
+ this.env.posbus.off("ticket-button-clicked", this);
+ }
+ //#endregion
+ //#region EVENT HANDLERS
+ _onCloseScreen() {
+ this.close();
+ }
+ async _onFilterSelected(event) {
+ this._state.ui.filter = event.detail.filter;
+ if (this._state.ui.filter == "SYNCED") {
await this._fetchSyncedOrders();
}
- _onClickOrderline({ detail: orderline }) {
- const order = this.getSelectedSyncedOrder();
- this._state.ui.selectedOrderlineIds[order.backendId] = orderline.id;
+ }
+ async _onSearch(event) {
+ Object.assign(this._state.ui.searchDetails, event.detail);
+ if (this._state.ui.filter == "SYNCED") {
+ this._state.syncedOrders.currentPage = 1;
+ await this._fetchSyncedOrders();
+ }
+ }
+ _onClickOrder({ detail: clickedOrder }) {
+ if (!clickedOrder || clickedOrder.locked) {
+ if (this._state.ui.selectedSyncedOrderId == clickedOrder.backendId) {
+ this._state.ui.selectedSyncedOrderId = null;
+ } else {
+ this._state.ui.selectedSyncedOrderId = clickedOrder.backendId;
+ }
+ if (!this.getSelectedOrderlineId()) {
+ // Automatically select the first orderline of the selected order.
+ const firstLine = clickedOrder.get_orderlines()[0];
+ if (firstLine) {
+ this._state.ui.selectedOrderlineIds[clickedOrder.backendId] = firstLine.id;
+ }
+ }
NumberBuffer.reset();
+ } else {
+ this._setOrder(clickedOrder);
}
- _onClickRefundOrderUid({ detail: orderUid }) {
- // Open the refund order.
- const refundOrder = this.env.pos.orders.find((order) => order.uid == orderUid);
- if (refundOrder) {
- this._setOrder(refundOrder);
- }
- }
- _onUpdateSelectedOrderline({ detail }) {
- const buffer = detail.buffer;
- const order = this.getSelectedSyncedOrder();
- if (!order) return NumberBuffer.reset();
-
- const selectedOrderlineId = this.getSelectedOrderlineId();
- const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
- if (!orderline) return NumberBuffer.reset();
-
- const toRefundDetail = this._getToRefundDetail(orderline);
- // When already linked to an order, do not modify the to refund quantity.
- if (toRefundDetail.destinationOrderUid) return NumberBuffer.reset();
-
- const refundableQty = toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty;
- if (refundableQty <= 0) return NumberBuffer.reset();
-
- if (buffer == null || buffer == '') {
- toRefundDetail.qty = 0;
- } else {
- const quantity = Math.abs(parse.float(buffer));
- if (quantity > refundableQty) {
- NumberBuffer.reset();
- this.showPopup('ErrorPopup', {
- title: this.env._t('Maximum Exceeded'),
- body: _.str.sprintf(
- this.env._t(
- 'The requested quantity to be refunded is higher than the ordered quantity. %s is requested while only %s can be refunded.'
- ),
- quantity,
- refundableQty
- ),
- });
- } else {
- toRefundDetail.qty = quantity;
- }
- }
- }
- async _onDoRefund() {
- const order = this.getSelectedSyncedOrder();
-
- if (this._doesOrderHaveSoleItem(order)) {
- if (!this._prepareAutoRefundOnOrder(order)) {
- // Don't proceed on refund if preparation returned false.
- return;
- }
- }
-
- if (!order) {
- this._state.ui.highlightHeaderNote = !this._state.ui.highlightHeaderNote;
- return;
- }
-
- const partner = order.get_partner();
-
- const allToRefundDetails = this._getRefundableDetails(partner);
- if (allToRefundDetails.length == 0) {
- this._state.ui.highlightHeaderNote = !this._state.ui.highlightHeaderNote;
- return;
- }
-
- // The order that will contain the refund orderlines.
- // Use the destinationOrder from props if the order to refund has the same
- // partner as the destinationOrder.
- const destinationOrder =
- this.props.destinationOrder && partner === this.props.destinationOrder.get_partner()
- ? this.props.destinationOrder
- : this._getEmptyOrder(partner);
-
- // Add orderline for each toRefundDetail to the destinationOrder.
- for (const refundDetail of allToRefundDetails) {
- const product = this.env.pos.db.get_product_by_id(refundDetail.orderline.productId);
- const options = this._prepareRefundOrderlineOptions(refundDetail);
- await destinationOrder.add_product(product, options);
- refundDetail.destinationOrderUid = destinationOrder.uid;
- }
-
- // Set the partner to the destinationOrder.
- if (partner && !destinationOrder.get_partner()) {
- destinationOrder.set_partner(partner);
- destinationOrder.updatePricelist(partner);
- }
-
- if (this.env.pos.get_order().cid !== destinationOrder.cid) {
- this.env.pos.set_order(destinationOrder);
- }
-
- this._onCloseScreen();
- }
- //#endregion
- //#region PUBLIC METHODS
- close() {
- /**
- * Automatically create new order when there is no currently active order.
- * Important in fiscal modules to keep the sequence of the orders.
- */
- if (this.env.pos.orders.length == 0) {
- this.env.pos.add_new_order();
- }
- super.close();
- }
- getSelectedSyncedOrder() {
- if (this._state.ui.filter == 'SYNCED') {
- return this._state.syncedOrders.cache[this._state.ui.selectedSyncedOrderId];
- } else {
- return null;
- }
- }
- getSelectedOrderlineId() {
- return this._state.ui.selectedOrderlineIds[this._state.ui.selectedSyncedOrderId];
- }
- /**
- * Override to conditionally show the new ticket button.
- */
- shouldShowNewOrderButton() {
- return true;
- }
- getFilteredOrderList() {
- if (this._state.ui.filter == 'SYNCED') return this._state.syncedOrders.toShow;
- const filterCheck = (order) => {
- if (this._state.ui.filter && this._state.ui.filter !== 'ACTIVE_ORDERS') {
- const screen = order.get_screen_data();
- return this._state.ui.filter === this._getScreenToStatusMap()[screen.name];
- }
- return true;
- };
- const { fieldName, searchTerm } = this._state.ui.searchDetails;
- const searchField = this._getSearchFields()[fieldName];
- const searchCheck = (order) => {
- if (!searchField) return true;
- const repr = searchField.repr(order);
- if (repr === null) return true;
- if (!searchTerm) return true;
- return repr && repr.toString().toLowerCase().includes(searchTerm.toLowerCase());
- };
- const predicate = (order) => {
- return filterCheck(order) && searchCheck(order);
- };
- return this._getOrderList().filter(predicate);
- }
- getDate(order) {
- return moment(order.validation_date).format('YYYY-MM-DD hh:mm A');
- }
- getTotal(order) {
- return this.env.pos.format_currency(order.get_total_with_tax());
- }
- getPartner(order) {
- return order.get_partner_name();
- }
- getCardholderName(order) {
- return order.get_cardholder_name();
- }
- getCashier(order) {
- return order.cashier ? order.cashier.name : '';
- }
- getStatus(order) {
- if (order.locked) {
- return this.env._t('Paid');
- } else {
- const screen = order.get_screen_data();
- return this._getOrderStates().get(this._getScreenToStatusMap()[screen.name]).text;
- }
- }
- /**
- * If the order is the only order and is empty
- */
- isDefaultOrderEmpty(order) {
- let status = this._getScreenToStatusMap()[order.get_screen_data().name];
- let productScreenStatus = this._getScreenToStatusMap().ProductScreen;
- return order.get_orderlines().length === 0 && this.env.pos.get_order_list().length === 1 &&
- status === productScreenStatus && order.get_paymentlines().length === 0;
- }
- /**
- * Hide the delete button if one of the payments is a 'done' electronic payment.
- */
- shouldHideDeleteButton(order) {
- return (
- this.isDefaultOrderEmpty(order)||
- order.locked ||
- order
- .get_paymentlines()
- .some((payment) => payment.is_electronic() && payment.get_payment_status() === 'done')
- );
- }
- isHighlighted(order) {
- if (this._state.ui.filter == 'SYNCED') {
- const selectedOrder = this.getSelectedSyncedOrder();
- return selectedOrder ? order.backendId == selectedOrder.backendId : false;
- } else {
- const activeOrder = this.env.pos.get_order();
- return activeOrder ? activeOrder.uid == order.uid : false;
- }
- }
- showCardholderName() {
- return this.env.pos.payment_methods.some((method) => method.use_payment_terminal);
- }
- getSearchBarConfig() {
- return {
- searchFields: new Map(
- Object.entries(this._getSearchFields()).map(([key, val]) => [key, val.displayName])
+ }
+ _onCreateNewOrder() {
+ this.env.pos.add_new_order();
+ this.showScreen("ProductScreen");
+ }
+ _selectNextOrder(currentOrder) {
+ const currentOrderIndex = this._getOrderList().indexOf(currentOrder);
+ const orderList = this._getOrderList();
+ this.env.pos.set_order(
+ orderList[currentOrderIndex + 1] || orderList[currentOrderIndex - 1]
+ );
+ }
+ async _onDeleteOrder({ detail: order }) {
+ const screen = order.get_screen_data();
+ if (
+ ["ProductScreen", "PaymentScreen"].includes(screen.name) &&
+ order.get_orderlines().length > 0
+ ) {
+ const { confirmed } = await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Existing orderlines"),
+ body: _.str.sprintf(
+ this.env._t(
+ "%s has a total amount of %s, are you sure you want to delete this order ?"
+ ),
+ order.name,
+ this.getTotal(order)
),
- filter: { show: true, options: this._getFilterOptions() },
- defaultSearchDetails: this._state.ui.searchDetails,
- defaultFilter: this._state.ui.filter,
- };
- }
- shouldShowPageControls() {
- return this._state.ui.filter == 'SYNCED' && this._getLastPage() > 1;
- }
- getPageNumber() {
- if (!this._state.syncedOrders.totalCount) {
- return `1/1`;
- } else {
- return `${this._state.syncedOrders.currentPage}/${this._getLastPage()}`;
+ });
+ if (!confirmed) {
+ return;
}
}
- getSelectedPartner() {
- const order = this.getSelectedSyncedOrder();
- return order ? order.get_partner() : null;
+ if (order && (await this._onBeforeDeleteOrder(order))) {
+ if (order === this.env.pos.get_order()) {
+ this._selectNextOrder(order);
+ }
+ this.env.pos.removeOrder(order);
}
- getHasItemsToRefund() {
- const order = this.getSelectedSyncedOrder();
- if (!order) return false;
- if (this._doesOrderHaveSoleItem(order)) return true;
- const total = Object.values(this.env.pos.toRefundLines)
- .filter(
- (toRefundDetail) =>
- toRefundDetail.orderline.orderUid === order.uid && !toRefundDetail.destinationOrderUid
- )
- .map((toRefundDetail) => toRefundDetail.qty)
- .reduce((acc, val) => acc + val, 0);
- return !this.env.pos.isProductQtyZero(total);
+ }
+ async _onNextPage() {
+ if (this._state.syncedOrders.currentPage < this._getLastPage()) {
+ this._state.syncedOrders.currentPage += 1;
+ await this._fetchSyncedOrders();
}
- //#endregion
- //#region PRIVATE METHODS
+ }
+ async _onPrevPage() {
+ if (this._state.syncedOrders.currentPage > 1) {
+ this._state.syncedOrders.currentPage -= 1;
+ await this._fetchSyncedOrders();
+ }
+ }
+ async _onInvoiceOrder({ detail: orderId }) {
+ this.env.pos._invalidateSyncedOrdersCache([orderId]);
+ await this._fetchSyncedOrders();
+ }
+ _onClickOrderline({ detail: orderline }) {
+ const order = this.getSelectedSyncedOrder();
+ this._state.ui.selectedOrderlineIds[order.backendId] = orderline.id;
+ NumberBuffer.reset();
+ }
+ _onClickRefundOrderUid({ detail: orderUid }) {
+ // Open the refund order.
+ const refundOrder = this.env.pos.orders.find((order) => order.uid == orderUid);
+ if (refundOrder) {
+ this._setOrder(refundOrder);
+ }
+ }
+ _onUpdateSelectedOrderline({ detail }) {
+ const buffer = detail.buffer;
+ const order = this.getSelectedSyncedOrder();
+ if (!order) {
+ return NumberBuffer.reset();
+ }
+
+ const selectedOrderlineId = this.getSelectedOrderlineId();
+ const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
+ if (!orderline) {
+ return NumberBuffer.reset();
+ }
+
+ const toRefundDetail = this._getToRefundDetail(orderline);
+ // When already linked to an order, do not modify the to refund quantity.
+ if (toRefundDetail.destinationOrderUid) {
+ return NumberBuffer.reset();
+ }
+
+ const refundableQty = toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty;
+ if (refundableQty <= 0) {
+ return NumberBuffer.reset();
+ }
+
+ if (buffer == null || buffer == "") {
+ toRefundDetail.qty = 0;
+ } else {
+ const quantity = Math.abs(parse.float(buffer));
+ if (quantity > refundableQty) {
+ NumberBuffer.reset();
+ this.showPopup("ErrorPopup", {
+ title: this.env._t("Maximum Exceeded"),
+ body: _.str.sprintf(
+ this.env._t(
+ "The requested quantity to be refunded is higher than the ordered quantity. %s is requested while only %s can be refunded."
+ ),
+ quantity,
+ refundableQty
+ ),
+ });
+ } else {
+ toRefundDetail.qty = quantity;
+ }
+ }
+ }
+ async _onDoRefund() {
+ const order = this.getSelectedSyncedOrder();
+
+ if (this._doesOrderHaveSoleItem(order)) {
+ if (!this._prepareAutoRefundOnOrder(order)) {
+ // Don't proceed on refund if preparation returned false.
+ return;
+ }
+ }
+
+ if (!order) {
+ this._state.ui.highlightHeaderNote = !this._state.ui.highlightHeaderNote;
+ return;
+ }
+
+ const partner = order.get_partner();
+
+ const allToRefundDetails = this._getRefundableDetails(partner);
+ if (allToRefundDetails.length == 0) {
+ this._state.ui.highlightHeaderNote = !this._state.ui.highlightHeaderNote;
+ return;
+ }
+
+ // The order that will contain the refund orderlines.
+ // Use the destinationOrder from props if the order to refund has the same
+ // partner as the destinationOrder.
+ const destinationOrder =
+ this.props.destinationOrder && partner === this.props.destinationOrder.get_partner()
+ ? this.props.destinationOrder
+ : this._getEmptyOrder(partner);
+
+ // Add orderline for each toRefundDetail to the destinationOrder.
+ for (const refundDetail of allToRefundDetails) {
+ const product = this.env.pos.db.get_product_by_id(refundDetail.orderline.productId);
+ const options = this._prepareRefundOrderlineOptions(refundDetail);
+ await destinationOrder.add_product(product, options);
+ refundDetail.destinationOrderUid = destinationOrder.uid;
+ }
+
+ // Set the partner to the destinationOrder.
+ if (partner && !destinationOrder.get_partner()) {
+ destinationOrder.set_partner(partner);
+ destinationOrder.updatePricelist(partner);
+ }
+
+ if (this.env.pos.get_order().cid !== destinationOrder.cid) {
+ this.env.pos.set_order(destinationOrder);
+ }
+
+ this._onCloseScreen();
+ }
+ //#endregion
+ //#region PUBLIC METHODS
+ close() {
/**
- * Find the empty order with the following priority:
- * - The empty order with the same parter as the provided.
- * - The first empty order without a partner.
- * - If no empty order, create a new one.
- * @param {Object | null} partner
- * @returns {boolean}
+ * Automatically create new order when there is no currently active order.
+ * Important in fiscal modules to keep the sequence of the orders.
*/
- _getEmptyOrder(partner) {
- let emptyOrderForPartner = null;
- let emptyOrder = null;
- for (const order of this.env.pos.orders) {
- if (order.get_orderlines().length === 0 && order.get_paymentlines().length === 0) {
- if (order.get_partner() === partner) {
- emptyOrderForPartner = order;
- break;
- } else if (!order.get_partner() && emptyOrder === null) {
- // If emptyOrderForPartner is not found, we will use the first empty order.
- emptyOrder = order;
- }
+ if (this.env.pos.orders.length == 0) {
+ this.env.pos.add_new_order();
+ }
+ super.close();
+ }
+ getSelectedSyncedOrder() {
+ if (this._state.ui.filter == "SYNCED") {
+ return this._state.syncedOrders.cache[this._state.ui.selectedSyncedOrderId];
+ } else {
+ return null;
+ }
+ }
+ getSelectedOrderlineId() {
+ return this._state.ui.selectedOrderlineIds[this._state.ui.selectedSyncedOrderId];
+ }
+ /**
+ * Override to conditionally show the new ticket button.
+ */
+ shouldShowNewOrderButton() {
+ return true;
+ }
+ getFilteredOrderList() {
+ if (this._state.ui.filter == "SYNCED") {
+ return this._state.syncedOrders.toShow;
+ }
+ const filterCheck = (order) => {
+ if (this._state.ui.filter && this._state.ui.filter !== "ACTIVE_ORDERS") {
+ const screen = order.get_screen_data();
+ return this._state.ui.filter === this._getScreenToStatusMap()[screen.name];
+ }
+ return true;
+ };
+ const { fieldName, searchTerm } = this._state.ui.searchDetails;
+ const searchField = this._getSearchFields()[fieldName];
+ const searchCheck = (order) => {
+ if (!searchField) {
+ return true;
+ }
+ const repr = searchField.repr(order);
+ if (repr === null) {
+ return true;
+ }
+ if (!searchTerm) {
+ return true;
+ }
+ return repr && repr.toString().toLowerCase().includes(searchTerm.toLowerCase());
+ };
+ const predicate = (order) => {
+ return filterCheck(order) && searchCheck(order);
+ };
+ return this._getOrderList().filter(predicate);
+ }
+ getDate(order) {
+ return moment(order.validation_date).format("YYYY-MM-DD hh:mm A");
+ }
+ getTotal(order) {
+ return this.env.pos.format_currency(order.get_total_with_tax());
+ }
+ getPartner(order) {
+ return order.get_partner_name();
+ }
+ getCardholderName(order) {
+ return order.get_cardholder_name();
+ }
+ getCashier(order) {
+ return order.cashier ? order.cashier.name : "";
+ }
+ getStatus(order) {
+ if (order.locked) {
+ return this.env._t("Paid");
+ } else {
+ const screen = order.get_screen_data();
+ return this._getOrderStates().get(this._getScreenToStatusMap()[screen.name]).text;
+ }
+ }
+ /**
+ * If the order is the only order and is empty
+ */
+ isDefaultOrderEmpty(order) {
+ const status = this._getScreenToStatusMap()[order.get_screen_data().name];
+ const productScreenStatus = this._getScreenToStatusMap().ProductScreen;
+ return (
+ order.get_orderlines().length === 0 &&
+ this.env.pos.get_order_list().length === 1 &&
+ status === productScreenStatus &&
+ order.get_paymentlines().length === 0
+ );
+ }
+ /**
+ * Hide the delete button if one of the payments is a 'done' electronic payment.
+ */
+ shouldHideDeleteButton(order) {
+ return (
+ this.isDefaultOrderEmpty(order) ||
+ order.locked ||
+ order
+ .get_paymentlines()
+ .some(
+ (payment) => payment.is_electronic() && payment.get_payment_status() === "done"
+ )
+ );
+ }
+ isHighlighted(order) {
+ if (this._state.ui.filter == "SYNCED") {
+ const selectedOrder = this.getSelectedSyncedOrder();
+ return selectedOrder ? order.backendId == selectedOrder.backendId : false;
+ } else {
+ const activeOrder = this.env.pos.get_order();
+ return activeOrder ? activeOrder.uid == order.uid : false;
+ }
+ }
+ showCardholderName() {
+ return this.env.pos.payment_methods.some((method) => method.use_payment_terminal);
+ }
+ getSearchBarConfig() {
+ return {
+ searchFields: new Map(
+ Object.entries(this._getSearchFields()).map(([key, val]) => [key, val.displayName])
+ ),
+ filter: { show: true, options: this._getFilterOptions() },
+ defaultSearchDetails: this._state.ui.searchDetails,
+ defaultFilter: this._state.ui.filter,
+ };
+ }
+ shouldShowPageControls() {
+ return this._state.ui.filter == "SYNCED" && this._getLastPage() > 1;
+ }
+ getPageNumber() {
+ if (!this._state.syncedOrders.totalCount) {
+ return `1/1`;
+ } else {
+ return `${this._state.syncedOrders.currentPage}/${this._getLastPage()}`;
+ }
+ }
+ getSelectedPartner() {
+ const order = this.getSelectedSyncedOrder();
+ return order ? order.get_partner() : null;
+ }
+ getHasItemsToRefund() {
+ const order = this.getSelectedSyncedOrder();
+ if (!order) {
+ return false;
+ }
+ if (this._doesOrderHaveSoleItem(order)) {
+ return true;
+ }
+ const total = Object.values(this.env.pos.toRefundLines)
+ .filter(
+ (toRefundDetail) =>
+ toRefundDetail.orderline.orderUid === order.uid &&
+ !toRefundDetail.destinationOrderUid
+ )
+ .map((toRefundDetail) => toRefundDetail.qty)
+ .reduce((acc, val) => acc + val, 0);
+ return !this.env.pos.isProductQtyZero(total);
+ }
+ //#endregion
+ //#region PRIVATE METHODS
+ /**
+ * Find the empty order with the following priority:
+ * - The empty order with the same parter as the provided.
+ * - The first empty order without a partner.
+ * - If no empty order, create a new one.
+ * @param {Object | null} partner
+ * @returns {boolean}
+ */
+ _getEmptyOrder(partner) {
+ let emptyOrderForPartner = null;
+ let emptyOrder = null;
+ for (const order of this.env.pos.orders) {
+ if (order.get_orderlines().length === 0 && order.get_paymentlines().length === 0) {
+ if (order.get_partner() === partner) {
+ emptyOrderForPartner = order;
+ break;
+ } else if (!order.get_partner() && emptyOrder === null) {
+ // If emptyOrderForPartner is not found, we will use the first empty order.
+ emptyOrder = order;
}
}
- return emptyOrderForPartner || emptyOrder || this.env.pos.add_new_order();
}
- _doesOrderHaveSoleItem(order) {
- const orderlines = order.get_orderlines();
- if (orderlines.length !== 1) return false;
- const theOrderline = orderlines[0];
- const refundableQty = theOrderline.get_quantity() - theOrderline.refunded_qty;
- return this.env.pos.isProductQtyZero(refundableQty - 1);
+ return emptyOrderForPartner || emptyOrder || this.env.pos.add_new_order();
+ }
+ _doesOrderHaveSoleItem(order) {
+ const orderlines = order.get_orderlines();
+ if (orderlines.length !== 1) {
+ return false;
+ }
+ const theOrderline = orderlines[0];
+ const refundableQty = theOrderline.get_quantity() - theOrderline.refunded_qty;
+ return this.env.pos.isProductQtyZero(refundableQty - 1);
+ }
+ _prepareAutoRefundOnOrder(order) {
+ const selectedOrderlineId = this.getSelectedOrderlineId();
+ const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
+ if (!orderline) {
+ return false;
}
- _prepareAutoRefundOnOrder(order) {
- const selectedOrderlineId = this.getSelectedOrderlineId();
- const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
- if (!orderline) return false;
- const toRefundDetail = this._getToRefundDetail(orderline);
- const refundableQty = orderline.get_quantity() - orderline.refunded_qty;
- if (this.env.pos.isProductQtyZero(refundableQty - 1)) {
- toRefundDetail.qty = 1;
- }
- return true;
+ const toRefundDetail = this._getToRefundDetail(orderline);
+ const refundableQty = orderline.get_quantity() - orderline.refunded_qty;
+ if (this.env.pos.isProductQtyZero(refundableQty - 1)) {
+ toRefundDetail.qty = 1;
}
- /**
- * Returns the corresponding toRefundDetail of the given orderline.
- * SIDE-EFFECT: Automatically creates a toRefundDetail object for
- * the given orderline if it doesn't exist and returns it.
- * @param {models.Orderline} orderline
- * @returns
- */
- _getToRefundDetail(orderline) {
- if (orderline.id in this.env.pos.toRefundLines) {
- return this.env.pos.toRefundLines[orderline.id];
- } else {
- const partner = orderline.order.get_partner();
- const orderPartnerId = partner ? partner.id : false;
- const newToRefundDetail = {
- qty: 0,
- orderline: {
- id: orderline.id,
- productId: orderline.product.id,
- price: orderline.price,
- qty: orderline.quantity,
- refundedQty: orderline.refunded_qty,
- orderUid: orderline.order.uid,
- orderBackendId: orderline.order.backendId,
- orderPartnerId,
- tax_ids: orderline.get_taxes().map(tax => tax.id),
- discount: orderline.discount,
- },
- destinationOrderUid: false,
- };
- this.env.pos.toRefundLines[orderline.id] = newToRefundDetail;
- return newToRefundDetail;
- }
- }
- /**
- * Select the lines from toRefundLines, as they can come from different orders.
- * Returns only details that:
- * - The quantity to refund is not zero
- * - Filtered by partner (optional)
- * - It's not yet linked to an active order (no destinationOrderUid)
- *
- * @param {Object} partner (optional)
- * @returns {Array} refundableDetails
- */
- _getRefundableDetails(partner) {
- return Object.values(this.env.pos.toRefundLines).filter(
- ({ qty, orderline, destinationOrderUid }) =>
- !this.env.pos.isProductQtyZero(qty) &&
- (partner ? orderline.orderPartnerId == partner.id : true) &&
- !destinationOrderUid
- );
- }
- /**
- * Prepares the options to add a refund orderline.
- *
- * @param {Object} toRefundDetail
- * @returns {Object}
- */
- _prepareRefundOrderlineOptions(toRefundDetail) {
- const { qty, orderline } = toRefundDetail;
- return {
- quantity: -qty,
- price: orderline.price,
- extras: { price_manually_set: true },
- merge: false,
- refunded_orderline_id: orderline.id,
- tax_ids: orderline.tax_ids,
- discount: orderline.discount,
- }
- }
- _setOrder(order) {
- this.env.pos.set_order(order);
- this.close();
- }
- _getOrderList() {
- return this.env.pos.get_order_list();
- }
- _getFilterOptions() {
- const orderStates = this._getOrderStates();
- orderStates.set('SYNCED', { text: this.env._t('Paid') });
- return orderStates;
- }
- /**
- * @returns {Record string, displayName: string, modelField: string }>}
- */
- _getSearchFields() {
- const fields = {
- RECEIPT_NUMBER: {
- repr: (order) => order.name,
- displayName: this.env._t('Receipt Number'),
- modelField: 'pos_reference',
- },
- DATE: {
- repr: (order) => moment(order.creation_date).format('YYYY-MM-DD hh:mm A'),
- displayName: this.env._t('Date'),
- modelField: 'date_order',
- },
- PARTNER: {
- repr: (order) => order.get_partner_name(),
- displayName: this.env._t('Customer'),
- modelField: 'partner_id.display_name',
+ return true;
+ }
+ /**
+ * Returns the corresponding toRefundDetail of the given orderline.
+ * SIDE-EFFECT: Automatically creates a toRefundDetail object for
+ * the given orderline if it doesn't exist and returns it.
+ * @param {models.Orderline} orderline
+ * @returns
+ */
+ _getToRefundDetail(orderline) {
+ if (orderline.id in this.env.pos.toRefundLines) {
+ return this.env.pos.toRefundLines[orderline.id];
+ } else {
+ const partner = orderline.order.get_partner();
+ const orderPartnerId = partner ? partner.id : false;
+ const newToRefundDetail = {
+ qty: 0,
+ orderline: {
+ id: orderline.id,
+ productId: orderline.product.id,
+ price: orderline.price,
+ qty: orderline.quantity,
+ refundedQty: orderline.refunded_qty,
+ orderUid: orderline.order.uid,
+ orderBackendId: orderline.order.backendId,
+ orderPartnerId,
+ tax_ids: orderline.get_taxes().map((tax) => tax.id),
+ discount: orderline.discount,
},
+ destinationOrderUid: false,
};
-
- if (this.showCardholderName()) {
- fields.CARDHOLDER_NAME = {
- repr: (order) => order.get_cardholder_name(),
- displayName: this.env._t('Cardholder Name'),
- modelField: 'payment_ids.cardholder_name',
- };
- }
-
- return fields;
+ this.env.pos.toRefundLines[orderline.id] = newToRefundDetail;
+ return newToRefundDetail;
}
- /**
- * Maps the order screen params to order status.
- */
- _getScreenToStatusMap() {
- return {
- ProductScreen: 'ONGOING',
- PaymentScreen: 'PAYMENT',
- ReceiptScreen: 'RECEIPT',
+ }
+ /**
+ * Select the lines from toRefundLines, as they can come from different orders.
+ * Returns only details that:
+ * - The quantity to refund is not zero
+ * - Filtered by partner (optional)
+ * - It's not yet linked to an active order (no destinationOrderUid)
+ *
+ * @param {Object} partner (optional)
+ * @returns {Array} refundableDetails
+ */
+ _getRefundableDetails(partner) {
+ return Object.values(this.env.pos.toRefundLines).filter(
+ ({ qty, orderline, destinationOrderUid }) =>
+ !this.env.pos.isProductQtyZero(qty) &&
+ (partner ? orderline.orderPartnerId == partner.id : true) &&
+ !destinationOrderUid
+ );
+ }
+ /**
+ * Prepares the options to add a refund orderline.
+ *
+ * @param {Object} toRefundDetail
+ * @returns {Object}
+ */
+ _prepareRefundOrderlineOptions(toRefundDetail) {
+ const { qty, orderline } = toRefundDetail;
+ return {
+ quantity: -qty,
+ price: orderline.price,
+ extras: { price_manually_set: true },
+ merge: false,
+ refunded_orderline_id: orderline.id,
+ tax_ids: orderline.tax_ids,
+ discount: orderline.discount,
+ };
+ }
+ _setOrder(order) {
+ this.env.pos.set_order(order);
+ this.close();
+ }
+ _getOrderList() {
+ return this.env.pos.get_order_list();
+ }
+ _getFilterOptions() {
+ const orderStates = this._getOrderStates();
+ orderStates.set("SYNCED", { text: this.env._t("Paid") });
+ return orderStates;
+ }
+ /**
+ * @returns {Record string, displayName: string, modelField: string }>}
+ */
+ _getSearchFields() {
+ const fields = {
+ RECEIPT_NUMBER: {
+ repr: (order) => order.name,
+ displayName: this.env._t("Receipt Number"),
+ modelField: "pos_reference",
+ },
+ DATE: {
+ repr: (order) => moment(order.creation_date).format("YYYY-MM-DD hh:mm A"),
+ displayName: this.env._t("Date"),
+ modelField: "date_order",
+ },
+ PARTNER: {
+ repr: (order) => order.get_partner_name(),
+ displayName: this.env._t("Customer"),
+ modelField: "partner_id.display_name",
+ },
+ };
+
+ if (this.showCardholderName()) {
+ fields.CARDHOLDER_NAME = {
+ repr: (order) => order.get_cardholder_name(),
+ displayName: this.env._t("Cardholder Name"),
+ modelField: "payment_ids.cardholder_name",
};
}
- /**
- * Override to do something before deleting the order.
- * Make sure to return true to proceed on deleting the order.
- * @param {*} order
- * @returns {boolean}
- */
- async _onBeforeDeleteOrder(order) {
- return true;
+
+ return fields;
+ }
+ /**
+ * Maps the order screen params to order status.
+ */
+ _getScreenToStatusMap() {
+ return {
+ ProductScreen: "ONGOING",
+ PaymentScreen: "PAYMENT",
+ ReceiptScreen: "RECEIPT",
+ };
+ }
+ /**
+ * Override to do something before deleting the order.
+ * Make sure to return true to proceed on deleting the order.
+ * @param {*} order
+ * @returns {boolean}
+ */
+ async _onBeforeDeleteOrder(order) {
+ return true;
+ }
+ _getOrderStates() {
+ // We need the items to be ordered, therefore, Map is used instead of normal object.
+ const states = new Map();
+ states.set("ACTIVE_ORDERS", {
+ text: this.env._t("All active orders"),
+ });
+ // The spaces are important to make sure the following states
+ // are under the category of `All active orders`.
+ states.set("ONGOING", {
+ text: this.env._t("Ongoing"),
+ indented: true,
+ });
+ states.set("PAYMENT", {
+ text: this.env._t("Payment"),
+ indented: true,
+ });
+ states.set("RECEIPT", {
+ text: this.env._t("Receipt"),
+ indented: true,
+ });
+ return states;
+ }
+ //#region SEARCH SYNCED ORDERS
+ _computeSyncedOrdersDomain() {
+ const { fieldName, searchTerm } = this._state.ui.searchDetails;
+ if (!searchTerm) {
+ return [];
}
- _getOrderStates() {
- // We need the items to be ordered, therefore, Map is used instead of normal object.
- const states = new Map();
- states.set('ACTIVE_ORDERS', {
- text: this.env._t('All active orders'),
- });
- // The spaces are important to make sure the following states
- // are under the category of `All active orders`.
- states.set('ONGOING', {
- text: this.env._t('Ongoing'),
- indented: true,
- });
- states.set('PAYMENT', {
- text: this.env._t('Payment'),
- indented: true,
- });
- states.set('RECEIPT', {
- text: this.env._t('Receipt'),
- indented: true,
- });
- return states;
+ const modelField = this._getSearchFields()[fieldName].modelField;
+ if (modelField) {
+ return [[modelField, "ilike", `%${searchTerm}%`]];
+ } else {
+ return [];
}
- //#region SEARCH SYNCED ORDERS
- _computeSyncedOrdersDomain() {
- const { fieldName, searchTerm } = this._state.ui.searchDetails;
- if (!searchTerm) return [];
- const modelField = this._getSearchFields()[fieldName].modelField;
- if (modelField) {
- return [[modelField, 'ilike', `%${searchTerm}%`]];
- } else {
- return [];
- }
- }
- /**
- * Fetches the done orders from the backend that needs to be shown.
- * If the order is already in cache, the full information about that
- * order is not fetched anymore, instead, we use info from cache.
- */
- async _fetchSyncedOrders() {
- const domain = this._computeSyncedOrdersDomain();
- const limit = this._state.syncedOrders.nPerPage;
- const offset = (this._state.syncedOrders.currentPage - 1) * this._state.syncedOrders.nPerPage;
- const { ids, totalCount } = await this.rpc({
- model: 'pos.order',
- method: 'search_paid_order_ids',
- kwargs: { config_id: this.env.pos.config.id, domain, limit, offset },
+ }
+ /**
+ * Fetches the done orders from the backend that needs to be shown.
+ * If the order is already in cache, the full information about that
+ * order is not fetched anymore, instead, we use info from cache.
+ */
+ async _fetchSyncedOrders() {
+ const domain = this._computeSyncedOrdersDomain();
+ const limit = this._state.syncedOrders.nPerPage;
+ const offset =
+ (this._state.syncedOrders.currentPage - 1) * this._state.syncedOrders.nPerPage;
+ const { ids, totalCount } = await this.rpc({
+ model: "pos.order",
+ method: "search_paid_order_ids",
+ kwargs: { config_id: this.env.pos.config.id, domain, limit, offset },
+ context: this.env.session.user_context,
+ });
+ const idsNotInCache = ids.filter((id) => !(id in this._state.syncedOrders.cache));
+ if (idsNotInCache.length > 0) {
+ const fetchedOrders = await this.rpc({
+ model: "pos.order",
+ method: "export_for_ui",
+ args: [idsNotInCache],
context: this.env.session.user_context,
});
- const idsNotInCache = ids.filter((id) => !(id in this._state.syncedOrders.cache));
- if (idsNotInCache.length > 0) {
- const fetchedOrders = await this.rpc({
- model: 'pos.order',
- method: 'export_for_ui',
- args: [idsNotInCache],
- context: this.env.session.user_context,
- });
- // Check for missing products and partners and load them in the PoS
- await this.env.pos._loadMissingProducts(fetchedOrders);
- await this.env.pos._loadMissingPartners(fetchedOrders);
- // Cache these fetched orders so that next time, no need to fetch
- // them again, unless invalidated. See `_onInvoiceOrder`.
- fetchedOrders.forEach((order) => {
- this._state.syncedOrders.cache[order.id] = Order.create({}, { pos: this.env.pos, json: order });
- });
- }
- this._state.syncedOrders.totalCount = totalCount;
- this._state.syncedOrders.toShow = ids.map((id) => this._state.syncedOrders.cache[id]);
+ // Check for missing products and partners and load them in the PoS
+ await this.env.pos._loadMissingProducts(fetchedOrders);
+ await this.env.pos._loadMissingPartners(fetchedOrders);
+ // Cache these fetched orders so that next time, no need to fetch
+ // them again, unless invalidated. See `_onInvoiceOrder`.
+ fetchedOrders.forEach((order) => {
+ this._state.syncedOrders.cache[order.id] = Order.create(
+ {},
+ { pos: this.env.pos, json: order }
+ );
+ });
}
- _getLastPage() {
- const totalCount = this._state.syncedOrders.totalCount;
- const nPerPage = this._state.syncedOrders.nPerPage;
- const remainder = totalCount % nPerPage;
- if (remainder == 0) {
- return totalCount / nPerPage;
- } else {
- return Math.ceil(totalCount / nPerPage);
- }
- }
- //#endregion
- //#endregion
+ this._state.syncedOrders.totalCount = totalCount;
+ this._state.syncedOrders.toShow = ids.map((id) => this._state.syncedOrders.cache[id]);
}
- TicketScreen.template = 'TicketScreen';
- TicketScreen.defaultProps = {
- destinationOrder: null,
- // When passed as true, it will use the saved _state.ui as default
- // value when this component is reinstantiated.
- // After setting the default value, the _state.ui will be overridden
- // by the passed props.ui if there is any.
- reuseSavedUIState: false,
- ui: {},
- };
+ _getLastPage() {
+ const totalCount = this._state.syncedOrders.totalCount;
+ const nPerPage = this._state.syncedOrders.nPerPage;
+ const remainder = totalCount % nPerPage;
+ if (remainder == 0) {
+ return totalCount / nPerPage;
+ } else {
+ return Math.ceil(totalCount / nPerPage);
+ }
+ }
+ //#endregion
+ //#endregion
+}
+TicketScreen.template = "TicketScreen";
+TicketScreen.defaultProps = {
+ destinationOrder: null,
+ // When passed as true, it will use the saved _state.ui as default
+ // value when this component is reinstantiated.
+ // After setting the default value, the _state.ui will be overridden
+ // by the passed props.ui if there is any.
+ reuseSavedUIState: false,
+ ui: {},
+};
- Registries.Component.add(TicketScreen);
+Registries.Component.add(TicketScreen);
- return TicketScreen;
-});
+export default TicketScreen;
diff --git a/addons/point_of_sale/static/src/js/barcode_reader.js b/addons/point_of_sale/static/src/js/barcode_reader.js
index 0a9c4b0f908..702473b3e05 100644
--- a/addons/point_of_sale/static/src/js/barcode_reader.js
+++ b/addons/point_of_sale/static/src/js/barcode_reader.js
@@ -1,8 +1,7 @@
-odoo.define('point_of_sale.BarcodeReader', function (require) {
-"use strict";
+/** @odoo-module */
-var concurrency = require('web.concurrency');
-var core = require('web.core');
+import concurrency from "web.concurrency";
+import core from "web.core";
var Mutex = concurrency.Mutex;
// this module interfaces with the barcode reader. It assumes the barcode reader
@@ -10,11 +9,7 @@ var Mutex = concurrency.Mutex;
// and deactivate the barcode reader. Use set_action_callbacks to tell it
// what to do when it reads a barcode.
var BarcodeReader = core.Class.extend({
- actions:[
- 'product',
- 'cashier',
- 'client',
- ],
+ actions: ["product", "cashier", "client"],
init: function (attributes) {
this.mutex = new Mutex();
@@ -29,7 +24,7 @@ var BarcodeReader = core.Class.extend({
this.action_callback_stack = [];
- core.bus.on('barcode_scanned', this, function (barcode) {
+ core.bus.on("barcode_scanned", this, function (barcode) {
// use mutex to make sure scans are done one after the other
this.mutex.exec(async () => {
await this.scan(barcode);
@@ -59,7 +54,7 @@ var BarcodeReader = core.Class.extend({
}
},
- remove_action_callback: function(name, callback) {
+ remove_action_callback: function (name, callback) {
if (!callback) {
delete this.action_callbacks[name];
return;
@@ -105,13 +100,15 @@ var BarcodeReader = core.Class.extend({
},
scan: async function (code) {
- if (!code) return;
+ if (!code) {
+ return;
+ }
const callbacks = Object.keys(this.exclusive_callbacks).length
? this.exclusive_callbacks
: this.action_callbacks;
let parsed_results = this.barcode_parser.parse_barcode(code);
- if (! Array.isArray(parsed_results)) {
+ if (!Array.isArray(parsed_results)) {
parsed_results = [parsed_results];
}
for (const parsed_result of parsed_results) {
@@ -120,9 +117,9 @@ var BarcodeReader = core.Class.extend({
await cb(parsed_result);
}
} else if (callbacks.error) {
- [...callbacks.error].map(cb => cb(parsed_result));
+ [...callbacks.error].map((cb) => cb(parsed_result));
} else {
- console.warn('Ignored Barcode Scan:', parsed_result);
+ console.warn("Ignored Barcode Scan:", parsed_result);
}
}
},
@@ -137,23 +134,26 @@ var BarcodeReader = core.Class.extend({
}
this.remote_active = 1;
- function waitforbarcode(){
- return self.proxy.connection.rpc('/hw_proxy/scanner',{},{shadow: true, timeout:7500})
- .then(function (barcode) {
- if (!self.remote_scanning) {
- self.remote_active = 0;
- return;
+ function waitforbarcode() {
+ return self.proxy.connection
+ .rpc("/hw_proxy/scanner", {}, { shadow: true, timeout: 7500 })
+ .then(
+ function (barcode) {
+ if (!self.remote_scanning) {
+ self.remote_active = 0;
+ return;
+ }
+ self.scan(barcode);
+ waitforbarcode();
+ },
+ function () {
+ if (!self.remote_scanning) {
+ self.remote_active = 0;
+ return;
+ }
+ waitforbarcode();
}
- self.scan(barcode);
- waitforbarcode();
- },
- function () {
- if (!self.remote_scanning) {
- self.remote_active = 0;
- return;
- }
- waitforbarcode();
- });
+ );
}
waitforbarcode();
},
@@ -164,6 +164,4 @@ var BarcodeReader = core.Class.extend({
},
});
-return BarcodeReader;
-
-});
+export default BarcodeReader;
diff --git a/addons/point_of_sale/static/src/js/custom_hooks.js b/addons/point_of_sale/static/src/js/custom_hooks.js
index 180cbcdf177..a62686e291a 100644
--- a/addons/point_of_sale/static/src/js/custom_hooks.js
+++ b/addons/point_of_sale/static/src/js/custom_hooks.js
@@ -1,118 +1,116 @@
-odoo.define('point_of_sale.custom_hooks', function (require) {
- 'use strict';
+/** @odoo-module */
+const { onMounted, onPatched, onWillUnmount, useComponent } = owl;
- const { onMounted, onPatched, onWillUnmount, useComponent } = owl;
+/**
+ * Introduce error handlers in the component.
+ *
+ * IMPROVEMENT: This is a terrible hook. There could be a better way to handle
+ * the error when the order failed to sync.
+ */
+export function useErrorHandlers() {
+ const component = useComponent();
- /**
- * Introduce error handlers in the component.
- *
- * IMPROVEMENT: This is a terrible hook. There could be a better way to handle
- * the error when the order failed to sync.
- */
- function useErrorHandlers() {
- const component = useComponent();
-
- component._handlePushOrderError = async function (error) {
- // This error handler receives `error` equivalent to `error.message` of the rpc error.
- if (error.message === 'Backend Invoice') {
- await this.showPopup('ConfirmPopup', {
- title: this.env._t('Please print the invoice from the backend'),
- body:
- this.env._t(
- 'The order has been synchronized earlier. Please make the invoice from the backend for the order: '
- ) + error.data.order.name,
- });
- } else if (error.code < 0) {
- // XmlHttpRequest Errors
- const title = this.env._t('Unable to sync order');
- const body = this.env._t(
- 'Check the internet connection then try to sync again by clicking on the red wifi button (upper right of the screen).'
- );
- await this.showPopup('OfflineErrorPopup', { title, body });
- } else if (error.code === 200) {
- // OpenERP Server Errors
- await this.showPopup('ErrorTracebackPopup', {
- title: error.data.message || this.env._t('Server Error'),
- body:
- error.data.debug ||
- this.env._t('The server encountered an error while receiving your order.'),
- });
- } else if (error.code === 700) {
- // Sweden Fiscal module errors
- await this.showPopup('ErrorPopup', {
- title: this.env._t('Fiscal data module error'),
- body:
- error.data.error.status ||
- this.env._t('The fiscal data module encountered an error while receiving your order.'),
- });
- } else if (error.code === 701) {
- // Belgian Fiscal module errors
- let bodyMessage = "";
- if(error.error.errorCode)
- bodyMessage = "'" + error.error.errorCode + "': " + error.error.errorMessage;
- else
- bodyMessage = "Fiscal data module is not on.";
- await this.showPopup('ErrorPopup', {
- title: this.env._t('Fiscal data module error'),
- body: bodyMessage
- });
- } else {
- // ???
- await this.showPopup('ErrorPopup', {
- title: this.env._t('Unknown Error'),
- body: this.env._t(
- 'The order could not be sent to the server due to an unknown error'
+ component._handlePushOrderError = async function (error) {
+ // This error handler receives `error` equivalent to `error.message` of the rpc error.
+ if (error.message === "Backend Invoice") {
+ await this.showPopup("ConfirmPopup", {
+ title: this.env._t("Please print the invoice from the backend"),
+ body:
+ this.env._t(
+ "The order has been synchronized earlier. Please make the invoice from the backend for the order: "
+ ) + error.data.order.name,
+ });
+ } else if (error.code < 0) {
+ // XmlHttpRequest Errors
+ const title = this.env._t("Unable to sync order");
+ const body = this.env._t(
+ "Check the internet connection then try to sync again by clicking on the red wifi button (upper right of the screen)."
+ );
+ await this.showPopup("OfflineErrorPopup", { title, body });
+ } else if (error.code === 200) {
+ // OpenERP Server Errors
+ await this.showPopup("ErrorTracebackPopup", {
+ title: error.data.message || this.env._t("Server Error"),
+ body:
+ error.data.debug ||
+ this.env._t("The server encountered an error while receiving your order."),
+ });
+ } else if (error.code === 700) {
+ // Sweden Fiscal module errors
+ await this.showPopup("ErrorPopup", {
+ title: this.env._t("Fiscal data module error"),
+ body:
+ error.data.error.status ||
+ this.env._t(
+ "The fiscal data module encountered an error while receiving your order."
),
- });
- }
- };
- }
-
- function useAutoFocusToLast() {
- const current = useComponent();
- let target = null;
- function autofocus() {
- const prevTarget = target;
- const allInputs = current.el.querySelectorAll('input');
- target = allInputs[allInputs.length - 1];
- if (target && target !== prevTarget) {
- target.focus();
- target.selectionStart = target.selectionEnd = target.value.length;
+ });
+ } else if (error.code === 701) {
+ // Belgian Fiscal module errors
+ let bodyMessage = "";
+ if (error.error.errorCode) {
+ bodyMessage = "'" + error.error.errorCode + "': " + error.error.errorMessage;
+ } else {
+ bodyMessage = "Fiscal data module is not on.";
}
+ await this.showPopup("ErrorPopup", {
+ title: this.env._t("Fiscal data module error"),
+ body: bodyMessage,
+ });
+ } else {
+ // ???
+ await this.showPopup("ErrorPopup", {
+ title: this.env._t("Unknown Error"),
+ body: this.env._t(
+ "The order could not be sent to the server due to an unknown error"
+ ),
+ });
}
- onMounted(autofocus);
- onPatched(autofocus);
- }
+ };
+}
- function useBarcodeReader(callbackMap, exclusive = false) {
- const current = useComponent();
- const barcodeReader = current.env.barcode_reader;
- for (let [key, callback] of Object.entries(callbackMap)) {
- callbackMap[key] = callback.bind(current);
+export function useAutoFocusToLast() {
+ const current = useComponent();
+ let target = null;
+ function autofocus() {
+ const prevTarget = target;
+ const allInputs = current.el.querySelectorAll("input");
+ target = allInputs[allInputs.length - 1];
+ if (target && target !== prevTarget) {
+ target.focus();
+ target.selectionStart = target.selectionEnd = target.value.length;
}
- onMounted(() => {
- if (barcodeReader) {
- for (let key in callbackMap) {
- if (exclusive) {
- barcodeReader.set_exclusive_callback(key, callbackMap[key]);
- } else {
- barcodeReader.set_action_callback(key, callbackMap[key]);
- }
+ }
+ onMounted(autofocus);
+ onPatched(autofocus);
+}
+
+export function useBarcodeReader(callbackMap, exclusive = false) {
+ const current = useComponent();
+ const barcodeReader = current.env.barcode_reader;
+ for (const [key, callback] of Object.entries(callbackMap)) {
+ callbackMap[key] = callback.bind(current);
+ }
+ onMounted(() => {
+ if (barcodeReader) {
+ for (const key in callbackMap) {
+ if (exclusive) {
+ barcodeReader.set_exclusive_callback(key, callbackMap[key]);
+ } else {
+ barcodeReader.set_action_callback(key, callbackMap[key]);
}
}
- });
- onWillUnmount(() => {
- if (barcodeReader) {
- for (let key in callbackMap) {
- if (exclusive) {
- barcodeReader.remove_exclusive_callback(key, callbackMap[key]);
- } else {
- barcodeReader.remove_action_callback(key, callbackMap[key]);
- }
+ }
+ });
+ onWillUnmount(() => {
+ if (barcodeReader) {
+ for (const key in callbackMap) {
+ if (exclusive) {
+ barcodeReader.remove_exclusive_callback(key, callbackMap[key]);
+ } else {
+ barcodeReader.remove_action_callback(key, callbackMap[key]);
}
}
- });
- }
-
- return { useErrorHandlers, useAutoFocusToLast, useBarcodeReader };
-});
+ }
+ });
+}
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index 1322143043d..e685715283f 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -1,14 +1,12 @@
-odoo.define('point_of_sale.DB', function (require) {
-"use strict";
+/** @odoo-module */
-var core = require('web.core');
-var utils = require('web.utils');
+import core from "web.core";
+import utils from "web.utils";
/* The PosDB holds reference to data that is either
* - static: does not change between pos reloads
* - persistent : must stay between reloads ( orders )
*/
-
/**
* cache the data in memory to avoid roundtrips to the localstorage
*
@@ -21,15 +19,15 @@ var utils = require('web.utils');
const CACHE = {};
var PosDB = core.Class.extend({
- name: 'openerp_pos_db', //the prefix of the localstorage data
- limit: 100, // the maximum number of results returned by a search
- init: function(options){
+ name: "openerp_pos_db", //the prefix of the localstorage data
+ limit: 100, // the maximum number of results returned by a search
+ init: function (options) {
options = options || {};
this.name = options.name || this.name;
this.limit = options.limit || this.limit;
if (options.uuid) {
- this.name = this.name + '_' + options.uuid;
+ this.name = this.name + "_" + options.uuid;
}
this.product_by_id = {};
@@ -44,11 +42,11 @@ var PosDB = core.Class.extend({
this.partner_write_date = null;
this.category_by_id = {};
- this.root_category_id = 0;
+ this.root_category_id = 0;
this.category_products = {};
this.category_ancestors = {};
this.category_childs = {};
- this.category_parent = {};
+ this.category_parent = {};
this.category_search_string = {};
},
@@ -59,87 +57,87 @@ var PosDB = core.Class.extend({
*
* @param {string} uuid Unique identifier of the PoS Config linked to the current session.
*/
- set_uuid: function(uuid){
- this.name = this.name + '_' + uuid;
+ set_uuid: function (uuid) {
+ this.name = this.name + "_" + uuid;
},
/* returns the category object from its id. If you pass a list of id as parameters, you get
* a list of category objects.
*/
- get_category_by_id: function(categ_id){
- if(categ_id instanceof Array){
+ get_category_by_id: function (categ_id) {
+ if (categ_id instanceof Array) {
var list = [];
- for(var i = 0, len = categ_id.length; i < len; i++){
+ for (var i = 0, len = categ_id.length; i < len; i++) {
var cat = this.category_by_id[categ_id[i]];
- if(cat){
+ if (cat) {
list.push(cat);
- }else{
- console.error("get_category_by_id: no category has id:",categ_id[i]);
+ } else {
+ console.error("get_category_by_id: no category has id:", categ_id[i]);
}
}
return list;
- }else{
+ } else {
return this.category_by_id[categ_id];
}
},
/* returns a list of the category's child categories ids, or an empty list
* if a category has no childs */
- get_category_childs_ids: function(categ_id){
+ get_category_childs_ids: function (categ_id) {
return this.category_childs[categ_id] || [];
},
/* returns a list of all ancestors (parent, grand-parent, etc) categories ids
* starting from the root category to the direct parent */
- get_category_ancestors_ids: function(categ_id){
+ get_category_ancestors_ids: function (categ_id) {
return this.category_ancestors[categ_id] || [];
},
/* returns the parent category's id of a category, or the root_category_id if no parent.
* the root category is parent of itself. */
- get_category_parent_id: function(categ_id){
+ get_category_parent_id: function (categ_id) {
return this.category_parent[categ_id] || this.root_category_id;
},
/* adds categories definitions to the database. categories is a list of categories objects as
* returned by the openerp server. Categories must be inserted before the products or the
* product/ categories association may (will) not work properly */
- add_categories: function(categories){
+ add_categories: function (categories) {
var self = this;
- if(!this.category_by_id[this.root_category_id]){
+ if (!this.category_by_id[this.root_category_id]) {
this.category_by_id[this.root_category_id] = {
- id : this.root_category_id,
- name : 'Root',
+ id: this.root_category_id,
+ name: "Root",
};
}
- categories.forEach(function(cat){
+ categories.forEach(function (cat) {
self.category_by_id[cat.id] = cat;
});
- categories.forEach(function(cat){
+ categories.forEach(function (cat) {
var parent_id = cat.parent_id[0];
- if(!(parent_id && self.category_by_id[parent_id])){
+ if (!(parent_id && self.category_by_id[parent_id])) {
parent_id = self.root_category_id;
}
self.category_parent[cat.id] = parent_id;
- if(!self.category_childs[parent_id]){
+ if (!self.category_childs[parent_id]) {
self.category_childs[parent_id] = [];
}
self.category_childs[parent_id].push(cat.id);
});
- function make_ancestors(cat_id, ancestors){
+ function make_ancestors(cat_id, ancestors) {
self.category_ancestors[cat_id] = ancestors;
ancestors = ancestors.slice(0);
ancestors.push(cat_id);
var childs = self.category_childs[cat_id] || [];
- for(var i=0, len = childs.length; i < len; i++){
+ for (var i = 0, len = childs.length; i < len; i++) {
make_ancestors(childs[i], ancestors);
}
}
make_ancestors(this.root_category_id, []);
},
- category_contains: function(categ_id, product_id) {
+ category_contains: function (categ_id, product_id) {
var product = this.product_by_id[product_id];
if (product) {
var cid = product.pos_categ_id[0];
- while (cid && cid !== categ_id){
+ while (cid && cid !== categ_id) {
cid = this.category_parent[cid];
}
return !!cid;
@@ -147,139 +145,151 @@ var PosDB = core.Class.extend({
return false;
},
/* loads a record store from the database. returns default if nothing is found */
- load: function(store,deft){
- if(CACHE[store] !== undefined){
+ load: function (store, deft) {
+ if (CACHE[store] !== undefined) {
return CACHE[store];
}
- var data = localStorage[this.name + '_' + store];
- if(data !== undefined && data !== ""){
+ var data = localStorage[this.name + "_" + store];
+ if (data !== undefined && data !== "") {
data = JSON.parse(data);
CACHE[store] = data;
return data;
- }else{
+ } else {
return deft;
}
},
/* saves a record store to the database */
- save: function(store,data){
- localStorage[this.name + '_' + store] = JSON.stringify(data);
+ save: function (store, data) {
+ localStorage[this.name + "_" + store] = JSON.stringify(data);
CACHE[store] = data;
},
- _product_search_string: function(product){
+ _product_search_string: function (product) {
var str = product.display_name;
if (product.barcode) {
- str += '|' + product.barcode;
+ str += "|" + product.barcode;
}
if (product.default_code) {
- str += '|' + product.default_code;
+ str += "|" + product.default_code;
}
if (product.description) {
- str += '|' + product.description;
+ str += "|" + product.description;
}
if (product.description_sale) {
- str += '|' + product.description_sale;
+ str += "|" + product.description_sale;
}
- str = product.id + ':' + str.replace(/:/g,'') + '\n';
+ str = product.id + ":" + str.replace(/:/g, "") + "\n";
return str;
},
- add_products: function(products){
+ add_products: function (products) {
var stored_categories = this.product_by_category_id;
- if(!(products instanceof Array)){
+ if (!(products instanceof Array)) {
products = [products];
}
- for(var i = 0, len = products.length; i < len; i++){
+ for (var i = 0, len = products.length; i < len; i++) {
var product = products[i];
- if (product.id in this.product_by_id) continue;
- if (product.available_in_pos){
+ if (product.id in this.product_by_id) {
+ continue;
+ }
+ if (product.available_in_pos) {
var search_string = utils.unaccent(this._product_search_string(product));
- var categ_id = product.pos_categ_id ? product.pos_categ_id[0] : this.root_category_id;
+ var categ_id = product.pos_categ_id
+ ? product.pos_categ_id[0]
+ : this.root_category_id;
product.product_tmpl_id = product.product_tmpl_id[0];
- if(!stored_categories[categ_id]){
+ if (!stored_categories[categ_id]) {
stored_categories[categ_id] = [];
}
stored_categories[categ_id].push(product.id);
- if(this.category_search_string[categ_id] === undefined){
- this.category_search_string[categ_id] = '';
+ if (this.category_search_string[categ_id] === undefined) {
+ this.category_search_string[categ_id] = "";
}
this.category_search_string[categ_id] += search_string;
var ancestors = this.get_category_ancestors_ids(categ_id) || [];
- for(var j = 0, jlen = ancestors.length; j < jlen; j++){
+ for (var j = 0, jlen = ancestors.length; j < jlen; j++) {
var ancestor = ancestors[j];
- if(! stored_categories[ancestor]){
+ if (!stored_categories[ancestor]) {
stored_categories[ancestor] = [];
}
stored_categories[ancestor].push(product.id);
- if( this.category_search_string[ancestor] === undefined){
- this.category_search_string[ancestor] = '';
+ if (this.category_search_string[ancestor] === undefined) {
+ this.category_search_string[ancestor] = "";
}
this.category_search_string[ancestor] += search_string;
}
}
this.product_by_id[product.id] = product;
- if(product.barcode){
+ if (product.barcode) {
this.product_by_barcode[product.barcode] = product;
}
}
},
- add_packagings: function(product_packagings){
+ add_packagings: function (product_packagings) {
var self = this;
_.map(product_packagings, function (product_packaging) {
- if (_.find(self.product_by_id, {'id': product_packaging.product_id[0]})) {
+ if (_.find(self.product_by_id, { id: product_packaging.product_id[0] })) {
self.product_packaging_by_barcode[product_packaging.barcode] = product_packaging;
}
});
},
- _partner_search_string: function(partner){
- var str = partner.name || '';
- if(partner.barcode){
- str += '|' + partner.barcode;
+ _partner_search_string: function (partner) {
+ var str = partner.name || "";
+ if (partner.barcode) {
+ str += "|" + partner.barcode;
}
- if(partner.address){
- str += '|' + partner.address;
+ if (partner.address) {
+ str += "|" + partner.address;
}
- if(partner.phone){
- str += '|' + partner.phone.split(' ').join('');
+ if (partner.phone) {
+ str += "|" + partner.phone.split(" ").join("");
}
- if(partner.mobile){
- str += '|' + partner.mobile.split(' ').join('');
+ if (partner.mobile) {
+ str += "|" + partner.mobile.split(" ").join("");
}
- if(partner.email){
- str += '|' + partner.email;
+ if (partner.email) {
+ str += "|" + partner.email;
}
- if(partner.vat){
- str += '|' + partner.vat;
+ if (partner.vat) {
+ str += "|" + partner.vat;
}
- if(partner.parent_name){
- str += '|' + partner.parent_name;
+ if (partner.parent_name) {
+ str += "|" + partner.parent_name;
}
- str = '' + partner.id + ':' + str.replace(':', '').replace(/\n/g, ' ') + '\n';
+ str = "" + partner.id + ":" + str.replace(":", "").replace(/\n/g, " ") + "\n";
return str;
},
- add_partners: function(partners){
+ add_partners: function (partners) {
var updated_count = 0;
- var new_write_date = '';
+ var new_write_date = "";
var partner;
- for(var i = 0, len = partners.length; i < len; i++){
+ for (var i = 0, len = partners.length; i < len; i++) {
partner = partners[i];
- var local_partner_date = (this.partner_write_date || '').replace(/^(\d{4}-\d{2}-\d{2}) ((\d{2}:?){3})$/, '$1T$2Z');
- var dist_partner_date = (partner.write_date || '').replace(/^(\d{4}-\d{2}-\d{2}) ((\d{2}:?){3})$/, '$1T$2Z');
- if ( this.partner_write_date &&
- this.partner_by_id[partner.id] &&
- new Date(local_partner_date).getTime() + 1000 >=
- new Date(dist_partner_date).getTime() ) {
+ var local_partner_date = (this.partner_write_date || "").replace(
+ /^(\d{4}-\d{2}-\d{2}) ((\d{2}:?){3})$/,
+ "$1T$2Z"
+ );
+ var dist_partner_date = (partner.write_date || "").replace(
+ /^(\d{4}-\d{2}-\d{2}) ((\d{2}:?){3})$/,
+ "$1T$2Z"
+ );
+ if (
+ this.partner_write_date &&
+ this.partner_by_id[partner.id] &&
+ new Date(local_partner_date).getTime() + 1000 >=
+ new Date(dist_partner_date).getTime()
+ ) {
// FIXME: The write_date is stored with milisec precision in the database
// but the dates we get back are only precise to the second. This means when
// you read partners modified strictly after time X, you get back partners that were
// modified X - 1 sec ago.
continue;
- } else if ( new_write_date < partner.write_date ) {
- new_write_date = partner.write_date;
+ } else if (new_write_date < partner.write_date) {
+ new_write_date = partner.write_date;
}
if (!this.partner_by_id[partner.id]) {
this.partner_sorted.push(partner.id);
@@ -301,14 +311,15 @@ var PosDB = core.Class.extend({
for (var id in this.partner_by_id) {
partner = this.partner_by_id[id];
- if(partner.barcode){
+ if (partner.barcode) {
this.partner_by_barcode[partner.barcode] = partner;
}
- partner.address = (partner.street ? partner.street + ', ': '') +
- (partner.zip ? partner.zip + ', ': '') +
- (partner.city ? partner.city + ', ': '') +
- (partner.state_id ? partner.state_id[1] + ', ': '') +
- (partner.country_id ? partner.country_id[1]: '');
+ partner.address =
+ (partner.street ? partner.street + ", " : "") +
+ (partner.zip ? partner.zip + ", " : "") +
+ (partner.city ? partner.city + ", " : "") +
+ (partner.state_id ? partner.state_id[1] + ", " : "") +
+ (partner.country_id ? partner.country_id[1] : "");
this.partner_search_string += this._partner_search_string(partner);
}
@@ -316,77 +327,82 @@ var PosDB = core.Class.extend({
}
return updated_count;
},
- get_partner_write_date: function(){
+ get_partner_write_date: function () {
return this.partner_write_date || "1970-01-01 00:00:00";
},
- get_partner_by_id: function(id){
+ get_partner_by_id: function (id) {
return this.partner_by_id[id];
},
- get_partner_by_barcode: function(barcode){
+ get_partner_by_barcode: function (barcode) {
return this.partner_by_barcode[barcode];
},
- get_partners_sorted: function(max_count){
- max_count = max_count ? Math.min(this.partner_sorted.length, max_count) : this.partner_sorted.length;
+ get_partners_sorted: function (max_count) {
+ max_count = max_count
+ ? Math.min(this.partner_sorted.length, max_count)
+ : this.partner_sorted.length;
var partners = [];
for (var i = 0; i < max_count; i++) {
partners.push(this.partner_by_id[this.partner_sorted[i]]);
}
return partners;
},
- search_partner: function(query){
+ search_partner: function (query) {
try {
- query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.');
- query = query.replace(/ /g,'.+');
- var re = RegExp("([0-9]+):.*?"+utils.unaccent(query),"gi");
- }catch{
+ // eslint-disable-next-line no-useless-escape
+ query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g, ".");
+ query = query.replace(/ /g, ".+");
+ var re = RegExp("([0-9]+):.*?" + utils.unaccent(query), "gi");
+ } catch {
return [];
}
var results = [];
- for(var i = 0; i < this.limit; i++){
+ for (var i = 0; i < this.limit; i++) {
var r = re.exec(this.partner_search_string);
- if(r){
+ if (r) {
var id = Number(r[1]);
results.push(this.get_partner_by_id(id));
- }else{
+ } else {
break;
}
}
return results;
},
/* removes all the data from the database. TODO : being able to selectively remove data */
- clear: function(){
- for(var i = 0, len = arguments.length; i < len; i++){
- localStorage.removeItem(this.name + '_' + arguments[i]);
+ clear: function () {
+ for (var i = 0, len = arguments.length; i < len; i++) {
+ localStorage.removeItem(this.name + "_" + arguments[i]);
}
},
/* this internal methods returns the count of properties in an object. */
- _count_props : function(obj){
+ _count_props: function (obj) {
var count = 0;
- for(var prop in obj){
- if(obj.hasOwnProperty(prop)){
+ for (var prop in obj) {
+ if (Object.hasOwnProperty.call(obj, prop)) {
count++;
}
}
return count;
},
- get_product_by_id: function(id){
+ get_product_by_id: function (id) {
return this.product_by_id[id];
},
- get_product_by_barcode: function(barcode){
- if(this.product_by_barcode[barcode]){
+ get_product_by_barcode: function (barcode) {
+ if (this.product_by_barcode[barcode]) {
return this.product_by_barcode[barcode];
} else if (this.product_packaging_by_barcode[barcode]) {
return this.product_by_id[this.product_packaging_by_barcode[barcode].product_id[0]];
}
return undefined;
},
- get_product_by_category: function(category_id){
- var product_ids = this.product_by_category_id[category_id];
+ get_product_by_category: function (category_id) {
+ var product_ids = this.product_by_category_id[category_id];
var list = [];
if (product_ids) {
for (var i = 0, len = Math.min(product_ids.length, this.limit); i < len; i++) {
const product = this.product_by_id[product_ids[i]];
- if (!(product.active && product.available_in_pos)) continue;
+ if (!(product.active && product.available_in_pos)) {
+ continue;
+ }
list.push(product);
}
}
@@ -396,23 +412,26 @@ var PosDB = core.Class.extend({
* - a category that is or is a child of category_id,
* - a name, package or barcode containing the query (case insensitive)
*/
- search_product_in_category: function(category_id, query){
+ search_product_in_category: function (category_id, query) {
try {
- query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.');
- query = query.replace(/ /g,'.+');
- var re = RegExp("([0-9]+):.*?"+utils.unaccent(query),"gi");
- }catch{
+ // eslint-disable-next-line no-useless-escape
+ query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g, ".");
+ query = query.replace(/ /g, ".+");
+ var re = RegExp("([0-9]+):.*?" + utils.unaccent(query), "gi");
+ } catch {
return [];
}
var results = [];
- for(var i = 0; i < this.limit; i++){
+ for (var i = 0; i < this.limit; i++) {
var r = re.exec(this.category_search_string[category_id]);
- if(r){
+ if (r) {
var id = Number(r[1]);
const product = this.get_product_by_id(id);
- if (!(product.active && product.available_in_pos)) continue;
+ if (!(product.active && product.available_in_pos)) {
+ continue;
+ }
results.push(product);
- }else{
+ } else {
break;
}
}
@@ -422,11 +441,12 @@ var PosDB = core.Class.extend({
* true if the product belongs to one of the provided category
* or one of its child categories.
*/
- is_product_in_category: function(category_ids, product_id) {
+ is_product_in_category: function (category_ids, product_id) {
let cat = this.get_product_by_id(product_id).pos_categ_id[0];
while (cat) {
- for (let cat_id of category_ids) {
- if (cat == cat_id) { // The == is important, ids may be strings
+ for (const cat_id of category_ids) {
+ if (cat == cat_id) {
+ // The == is important, ids may be strings
return true;
}
}
@@ -436,15 +456,15 @@ var PosDB = core.Class.extend({
},
/* paid orders */
- add_order: function(order){
+ add_order: function (order) {
var order_id = order.uid;
- var orders = this.load('orders',[]);
+ var orders = this.load("orders", []);
// if the order was already stored, we overwrite its data
- for(var i = 0, len = orders.length; i < len; i++){
- if(orders[i].id === order_id){
+ for (var i = 0, len = orders.length; i < len; i++) {
+ if (orders[i].id === order_id) {
orders[i].data = order;
- this.save('orders',orders);
+ this.save("orders", orders);
return order_id;
}
}
@@ -453,27 +473,27 @@ var PosDB = core.Class.extend({
// that where already stored should already have been removed.
this.remove_unpaid_order(order);
- orders.push({id: order_id, data: order});
- this.save('orders',orders);
+ orders.push({ id: order_id, data: order });
+ this.save("orders", orders);
return order_id;
},
- remove_order: function(order_id){
- var orders = this.load('orders',[]);
- orders = _.filter(orders, function(order){
+ remove_order: function (order_id) {
+ var orders = this.load("orders", []);
+ orders = _.filter(orders, function (order) {
return order.id !== order_id;
});
- this.save('orders',orders);
+ this.save("orders", orders);
},
- remove_all_orders: function(){
- this.save('orders',[]);
+ remove_all_orders: function () {
+ this.save("orders", []);
},
- get_orders: function(){
- return this.load('orders',[]);
+ get_orders: function () {
+ return this.load("orders", []);
},
- get_order: function(order_id){
+ get_order: function (order_id) {
var orders = this.get_orders();
- for(var i = 0, len = orders.length; i < len; i++){
- if(orders[i].id === order_id){
+ for (var i = 0, len = orders.length; i < len; i++) {
+ if (orders[i].id === order_id) {
return orders[i];
}
}
@@ -481,35 +501,35 @@ var PosDB = core.Class.extend({
},
/* working orders */
- save_unpaid_order: function(order){
+ save_unpaid_order: function (order) {
var order_id = order.uid;
- var orders = this.load('unpaid_orders',[]);
+ var orders = this.load("unpaid_orders", []);
var serialized = order.export_as_JSON();
for (var i = 0; i < orders.length; i++) {
- if (orders[i].id === order_id){
+ if (orders[i].id === order_id) {
orders[i].data = serialized;
- this.save('unpaid_orders',orders);
+ this.save("unpaid_orders", orders);
return order_id;
}
}
- orders.push({id: order_id, data: serialized});
- this.save('unpaid_orders',orders);
+ orders.push({ id: order_id, data: serialized });
+ this.save("unpaid_orders", orders);
return order_id;
},
- remove_unpaid_order: function(order){
- var orders = this.load('unpaid_orders',[]);
- orders = _.filter(orders, function(o){
+ remove_unpaid_order: function (order) {
+ var orders = this.load("unpaid_orders", []);
+ orders = _.filter(orders, function (o) {
return o.id !== order.uid;
});
- this.save('unpaid_orders',orders);
+ this.save("unpaid_orders", orders);
},
- remove_all_unpaid_orders: function(){
- this.save('unpaid_orders',[]);
+ remove_all_unpaid_orders: function () {
+ this.save("unpaid_orders", []);
},
- get_unpaid_orders: function(){
- var saved = this.load('unpaid_orders',[]);
+ get_unpaid_orders: function () {
+ var saved = this.load("unpaid_orders", []);
var orders = [];
for (var i = 0; i < saved.length; i++) {
orders.push(saved[i].data);
@@ -521,9 +541,13 @@ var PosDB = core.Class.extend({
* @param {array} ids order_ids.
* @return {array