[REF] point_of_sale, *: remove class and component registries

*: l10n_co_pos, l10n_fr_pos_cert, l10n_gcc_pos, l10n_in_pos,
l10n_sa_pos, pos_adyen, pos_discount, pos_epson_printer,
pos_epson_printer_restaurant, pos_hr, pos_hr_restaurant, pos_loyalty,
pos_mercury, pos_restaurant, pos_sale, pos_sale, pos_sale, pos_sale,
pos_sale, pos_sale, pos_sale_loyalty, pos_sale_product_configurator,
pos_six, pos_six, pos_six, pos_six, pos_stripe, pos_stripe

Previously, the point of sale module used a custom inheritance system
for models and components. This system being non-standard, it makes it
hard for newcomers to understand how to use it or for people with
experience in the pos code base to understand how things are done
elsewhere. This commit removes the "registries" system from the pos
modules and adapts existing code to use standard inheritance mechanisms,
ie extending classes directly, and using patch to modify behaviour in
place.

task-3119628

closes odoo/odoo#109928

Related: odoo/enterprise#35795
Signed-off-by: Samuel Degueldre <sad@odoo.com>
Co-authored-by: Samuel Degueldre <sad@odoo.com>
This commit is contained in:
Rahul Prajapati
2023-01-23 16:01:27 +01:00
committed by Samuel Degueldre
co-authored by Samuel Degueldre
parent ae1cd3d5bb
commit b119cca37b
185 changed files with 5671 additions and 6763 deletions
@@ -1,30 +1,26 @@
/** @odoo-module */
import PaymentScreen from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
import Registries from "@point_of_sale/js/Registries";
import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
import { patch } from "@web/core/utils/patch";
import session from "web.session";
const L10nCoPosPaymentScreen = (PaymentScreen) =>
class extends PaymentScreen {
async _postPushOrderResolve(order, order_server_ids) {
try {
if (this.env.pos.is_colombian_country()) {
const result = await this.rpc({
model: "pos.order",
method: "search_read",
domain: [["id", "in", order_server_ids]],
fields: ["name"],
context: session.user_context,
});
order.set_l10n_co_dian(result[0].name || false);
}
} catch {
// FIXME this doesn't seem correct but is equivalent to return in finally which we had before.
patch(PaymentScreen.prototype, "l10n_co_pos.PaymentScreen", {
async _postPushOrderResolve(order, order_server_ids) {
const _super = this._super;
try {
if (this.env.pos.is_colombian_country()) {
const result = await this.rpc({
model: "pos.order",
method: "search_read",
domain: [["id", "in", order_server_ids]],
fields: ["name"],
context: session.user_context,
});
order.set_l10n_co_dian(result[0].name || false);
}
return super._postPushOrderResolve(...arguments);
} catch {
// FIXME this doesn't seem correct but is equivalent to return in finally which we had before.
}
};
Registries.Component.extend(PaymentScreen, L10nCoPosPaymentScreen);
export default PaymentScreen;
return _super(...arguments);
},
});
+24 -28
View File
@@ -1,33 +1,29 @@
/** @odoo-module */
import { PosGlobalState, Order } from "@point_of_sale/js/models";
import Registries from "@point_of_sale/js/Registries";
import { patch } from "@web/core/utils/patch";
const L10nCoPosGlobalState = (PosGlobalState) =>
class L10nCoPosGlobalState extends PosGlobalState {
is_colombian_country() {
return this.company.country.code === "CO";
}
};
Registries.Model.extend(PosGlobalState, L10nCoPosGlobalState);
patch(PosGlobalState.prototype, "l10n_co_pos.PosGlobalState", {
is_colombian_country() {
return this.company.country.code === "CO";
},
});
const L10nCoPosOrder = (Order) =>
class L10nCoPosOrder extends Order {
export_for_printing() {
var result = super.export_for_printing(...arguments);
result.l10n_co_dian = this.get_l10n_co_dian();
return result;
}
set_l10n_co_dian(l10n_co_dian) {
this.l10n_co_dian = l10n_co_dian;
}
get_l10n_co_dian() {
return this.l10n_co_dian;
}
wait_for_push_order() {
var result = super.wait_for_push_order(...arguments);
result = Boolean(result || this.pos.is_colombian_country());
return result;
}
};
Registries.Model.extend(Order, L10nCoPosOrder);
patch(Order.prototype, "l10n_co_pos.Order", {
export_for_printing() {
const result = this._super(...arguments);
result.l10n_co_dian = this.get_l10n_co_dian();
return result;
},
set_l10n_co_dian(l10n_co_dian) {
this.l10n_co_dian = l10n_co_dian;
},
get_l10n_co_dian() {
return this.l10n_co_dian;
},
wait_for_push_order() {
var result = this._super(...arguments);
result = Boolean(result || this.pos.is_colombian_country());
return result;
},
});
+15 -19
View File
@@ -1,24 +1,20 @@
/** @odoo-module */
import Chrome from "@point_of_sale/js/Chrome";
import Registries from "@point_of_sale/js/Registries";
import { Chrome } from "@point_of_sale/js/Chrome";
import { patch } from "@web/core/utils/patch";
import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup";
const PosFrCertChrome = (Chrome) =>
class extends Chrome {
async start() {
await super.start();
if (this.env.pos.is_french_country() && this.env.pos.pos_session.start_at) {
const now = Date.now();
const limitDate = new Date(this.env.pos.pos_session.start_at);
limitDate.setDate(limitDate.getDate() + 1);
if (limitDate.getTime() < now) {
const info = await this.env.pos.getClosePosInfo();
this.showPopup("ClosePosPopup", { info: info });
}
patch(Chrome.prototype, "l10n_fr_pos_cert.Chrome", {
async start() {
await this._super();
if (this.env.pos.is_french_country() && this.env.pos.pos_session.start_at) {
const now = Date.now();
const limitDate = new Date(this.env.pos.pos_session.start_at);
limitDate.setDate(limitDate.getDate() + 1);
if (limitDate.getTime() < now) {
const info = await this.env.pos.getClosePosInfo();
this.showPopup(ClosePosPopup, { info: info });
}
}
};
Registries.Component.extend(Chrome, PosFrCertChrome);
export default Chrome;
},
});
@@ -1,30 +1,26 @@
/** @odoo-module */
import PaymentScreen from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
import Registries from "@point_of_sale/js/Registries";
import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
import { patch } from "@web/core/utils/patch";
import session from "web.session";
const PosFrPaymentScreen = (PaymentScreen) =>
class extends PaymentScreen {
async _postPushOrderResolve(order, order_server_ids) {
try {
if (this.env.pos.is_french_country()) {
const result = await this.rpc({
model: "pos.order",
method: "search_read",
domain: [["id", "in", order_server_ids]],
fields: ["l10n_fr_hash"],
context: session.user_context,
});
order.set_l10n_fr_hash(result[0].l10n_fr_hash || false);
}
} catch {
// FIXME this doesn't seem correct but is equivalent to return in finally which we had before.
patch(PaymentScreen.prototype, "l10n_fr_pos_cert.PaymentScreen", {
async _postPushOrderResolve(order, order_server_ids) {
const _super = this._super;
try {
if (this.env.pos.is_french_country()) {
const result = await this.rpc({
model: "pos.order",
method: "search_read",
domain: [["id", "in", order_server_ids]],
fields: ["l10n_fr_hash"],
context: session.user_context,
});
order.set_l10n_fr_hash(result[0].l10n_fr_hash || false);
}
return super._postPushOrderResolve(...arguments);
} catch {
// FIXME this doesn't seem correct but is equivalent to return in finally which we had before.
}
};
Registries.Component.extend(PaymentScreen, PosFrPaymentScreen);
export default PaymentScreen;
return _super(...arguments);
},
});
@@ -1,25 +1,20 @@
/** @odoo-module */
import ClosePosPopup from "@point_of_sale/js/Popups/ClosePosPopup";
import Registries from "@point_of_sale/js/Registries";
import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup";
import { patch } from "@web/core/utils/patch";
const PosFrCertClosePopup = (ClosePosPopup) =>
class extends ClosePosPopup {
sessionIsOutdated() {
let isOutdated = false;
if (this.env.pos.is_french_country() && this.env.pos.pos_session.start_at) {
const now = Date.now();
const limitDate = new Date(this.env.pos.pos_session.start_at);
limitDate.setDate(limitDate.getDate() + 1);
isOutdated = limitDate < now;
}
return isOutdated;
patch(ClosePosPopup.prototype, "l10n_fr_pos_cert.ClosePosPopup", {
sessionIsOutdated() {
let isOutdated = false;
if (this.env.pos.is_french_country() && this.env.pos.pos_session.start_at) {
const now = Date.now();
const limitDate = new Date(this.env.pos.pos_session.start_at);
limitDate.setDate(limitDate.getDate() + 1);
isOutdated = limitDate < now;
}
canCancel() {
return super.canCancel() && !this.sessionIsOutdated();
}
};
Registries.Component.extend(ClosePosPopup, PosFrCertClosePopup);
export default ClosePosPopup;
return isOutdated;
},
canCancel() {
return this._super() && !this.sessionIsOutdated();
},
});
+74 -79
View File
@@ -3,91 +3,86 @@
import { Gui } from "@point_of_sale/js/Gui";
import { PosGlobalState, Order, Orderline } from "@point_of_sale/js/models";
import core from "web.core";
import Registries from "@point_of_sale/js/Registries";
import { patch } from "@web/core/utils/patch";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
var _t = core._t;
const L10nFrPosGlobalState = (PosGlobalState) =>
class L10nFrPosGlobalState extends PosGlobalState {
is_french_country() {
var french_countries = ["FR", "MF", "MQ", "NC", "PF", "RE", "GF", "GP", "TF"];
if (!this.company.country) {
Gui.showPopup("ErrorPopup", {
title: _t("Missing Country"),
body: _.str.sprintf(
_t("The company %s doesn't have a country set."),
this.company.name
),
});
return false;
}
return _.contains(french_countries, this.company.country.code);
patch(PosGlobalState.prototype, "l10n_fr_pos_cert.PosGlobalState", {
is_french_country() {
var french_countries = ["FR", "MF", "MQ", "NC", "PF", "RE", "GF", "GP", "TF"];
if (!this.company.country) {
Gui.showPopup(ErrorPopup, {
title: _t("Missing Country"),
body: _.str.sprintf(
_t("The company %s doesn't have a country set."),
this.company.name
),
});
return false;
}
disallowLineQuantityChange() {
const result = super.disallowLineQuantityChange(...arguments);
return this.is_french_country() || result;
}
};
Registries.Model.extend(PosGlobalState, L10nFrPosGlobalState);
return _.contains(french_countries, this.company.country.code);
},
disallowLineQuantityChange() {
const result = this._super(...arguments);
return this.is_french_country() || result;
},
});
const L10nFrOrder = (Order) =>
class L10nFrOrder extends Order {
constructor() {
super(...arguments);
this.l10n_fr_hash = this.l10n_fr_hash || false;
this.save_to_db();
patch(Order.prototype, "l10n_fr_pos_cert.Order", {
setup() {
this._super(...arguments);
this.l10n_fr_hash = this.l10n_fr_hash || false;
this.save_to_db();
},
export_for_printing() {
var result = this._super(...arguments);
result.l10n_fr_hash = this.get_l10n_fr_hash();
return result;
},
set_l10n_fr_hash(l10n_fr_hash) {
this.l10n_fr_hash = l10n_fr_hash;
},
get_l10n_fr_hash() {
return this.l10n_fr_hash;
},
wait_for_push_order() {
var result = this._super(...arguments);
result = Boolean(result || this.pos.is_french_country());
return result;
},
destroy(option) {
// SUGGESTION: It's probably more appropriate to apply this restriction
// in the TicketScreen.
if (
option &&
option.reason == "abandon" &&
this.pos.is_french_country() &&
this.get_orderlines().length
) {
Gui.showPopup(ErrorPopup, {
title: _t("Fiscal Data Module error"),
body: _t("Deleting of orders is not allowed."),
});
} else {
this._super(...arguments);
}
export_for_printing() {
var result = super.export_for_printing(...arguments);
result.l10n_fr_hash = this.get_l10n_fr_hash();
return result;
}
set_l10n_fr_hash(l10n_fr_hash) {
this.l10n_fr_hash = l10n_fr_hash;
}
get_l10n_fr_hash() {
return this.l10n_fr_hash;
}
wait_for_push_order() {
var result = super.wait_for_push_order(...arguments);
result = Boolean(result || this.pos.is_french_country());
return result;
}
destroy(option) {
// SUGGESTION: It's probably more appropriate to apply this restriction
// in the TicketScreen.
if (
option &&
option.reason == "abandon" &&
this.pos.is_french_country() &&
this.get_orderlines().length
) {
Gui.showPopup("ErrorPopup", {
title: _t("Fiscal Data Module error"),
body: _t("Deleting of orders is not allowed."),
});
} else {
super.destroy(...arguments);
}
}
};
Registries.Model.extend(Order, L10nFrOrder);
},
});
const L10nFrOrderline = (Orderline) =>
class L10nFrOrderline extends Orderline {
can_be_merged_with(orderline) {
const order = this.pos.get_order();
const orderlines = order.orderlines;
const lastOrderline = order.orderlines.at(orderlines.length - 1);
patch(Orderline.prototype, "l10n_fr_pos_cert.Orderline", {
can_be_merged_with(orderline) {
const order = this.pos.get_order();
const orderlines = order.orderlines;
const lastOrderline = order.orderlines.at(orderlines.length - 1);
if (
this.pos.is_french_country() &&
(lastOrderline.product.id !== orderline.product.id || lastOrderline.quantity < 0)
) {
return false;
} else {
return super.can_be_merged_with(...arguments);
}
if (
this.pos.is_french_country() &&
(lastOrderline.product.id !== orderline.product.id || lastOrderline.quantity < 0)
) {
return false;
} else {
return this._super(...arguments);
}
};
Registries.Model.extend(Orderline, L10nFrOrderline);
},
});
@@ -1,19 +1,16 @@
/** @odoo-module */
import OrderReceipt from "@point_of_sale/js/Screens/ReceiptScreen/OrderReceipt";
import Registries from "@point_of_sale/js/Registries";
import { OrderReceipt } from "@point_of_sale/js/Screens/ReceiptScreen/OrderReceipt";
import { patch } from "@web/core/utils/patch";
const OrderReceiptGCC = (OrderReceipt) =>
class extends OrderReceipt {
get receiptEnv() {
const receipt_render_env = super.receiptEnv;
const receipt = receipt_render_env.receipt;
const country = receipt_render_env.order.pos.company.country;
receipt.is_gcc_country = ["SA", "AE", "BH", "OM", "QA", "KW"].includes(
country && country.code
);
return receipt_render_env;
}
};
Registries.Component.extend(OrderReceipt, OrderReceiptGCC);
export default OrderReceiptGCC;
patch(OrderReceipt.prototype, "l10n_gcc_pos.OrderReceipt", {
get receiptEnv() {
const receipt_render_env = this._super();
const receipt = receipt_render_env.receipt;
const country = receipt_render_env.order.pos.company.country;
receipt.is_gcc_country = ["SA", "AE", "BH", "OM", "QA", "KW"].includes(
country && country.code
);
return receipt_render_env;
},
});
+8 -10
View File
@@ -1,14 +1,12 @@
/** @odoo-module */
import { Orderline } from "@point_of_sale/js/models";
import Registries from "@point_of_sale/js/Registries";
import { patch } from "@web/core/utils/patch";
const L10nInOrderline = (Orderline) =>
class L10nInOrderline extends Orderline {
export_for_printing() {
var line = super.export_for_printing(...arguments);
line.l10n_in_hsn_code = this.get_product().l10n_in_hsn_code;
return line;
}
};
Registries.Model.extend(Orderline, L10nInOrderline);
patch(Orderline.prototype, "l10n_in_pos.Orderline", {
export_for_printing() {
const line = this._super(...arguments);
line.l10n_in_hsn_code = this.get_product().l10n_in_hsn_code;
return line;
},
});
+68 -72
View File
@@ -1,79 +1,75 @@
/** @odoo-module */
import { Order } from "@point_of_sale/js/models";
import Registries from "@point_of_sale/js/Registries";
import { patch } from "@web/core/utils/patch";
const PosL10nSAOrder = (Order) =>
class PosL10nSAOrder extends Order {
export_for_printing() {
var result = super.export_for_printing(...arguments);
if (this.pos.company.country.code === "SA") {
result.is_settlement = this.is_settlement();
if (!result.is_settlement) {
const codeWriter = new window.ZXing.BrowserQRCodeSvgWriter();
const qr_values = this.compute_sa_qr_code(
result.company.name,
result.company.vat,
result.date.isostring,
result.total_with_tax,
result.total_tax
);
const qr_code_svg = new XMLSerializer().serializeToString(
codeWriter.write(qr_values, 150, 150)
);
result.qr_code = "data:image/svg+xml;base64," + window.btoa(qr_code_svg);
}
patch(Order.prototype, "l10n_sa_pos.Order", {
export_for_printing() {
const result = this._super(...arguments);
if (this.pos.company.country.code === "SA") {
result.is_settlement = this.is_settlement();
if (!result.is_settlement) {
const codeWriter = new window.ZXing.BrowserQRCodeSvgWriter();
const qr_values = this.compute_sa_qr_code(
result.company.name,
result.company.vat,
result.date.isostring,
result.total_with_tax,
result.total_tax
);
const qr_code_svg = new XMLSerializer().serializeToString(
codeWriter.write(qr_values, 150, 150)
);
result.qr_code = "data:image/svg+xml;base64," + window.btoa(qr_code_svg);
}
return result;
}
/**
* If the order is empty (there are no products)
* and all "pay_later" payments are negative,
* we are settling a customer's account.
* If the module pos_settle_due is not installed,
* the function always returns false (since "pay_later" doesn't exist)
* @returns {boolean} true if the current order is a settlement, else false
*/
is_settlement() {
return (
this.is_empty() &&
!!this.paymentlines.filter(
(paymentline) =>
paymentline.payment_method.type === "pay_later" && paymentline.amount < 0
).length
);
return result;
},
/**
* If the order is empty (there are no products)
* and all "pay_later" payments are negative,
* we are settling a customer's account.
* If the module pos_settle_due is not installed,
* the function always returns false (since "pay_later" doesn't exist)
* @returns {boolean} true if the current order is a settlement, else false
*/
is_settlement() {
return (
this.is_empty() &&
!!this.paymentlines.filter(
(paymentline) =>
paymentline.payment_method.type === "pay_later" && paymentline.amount < 0
).length
);
},
compute_sa_qr_code(name, vat, date_isostring, amount_total, amount_tax) {
/* Generate the qr code for Saudi e-invoicing. Specs are available at the following link at page 23
https://zatca.gov.sa/ar/E-Invoicing/SystemsDevelopers/Documents/20210528_ZATCA_Electronic_Invoice_Security_Features_Implementation_Standards_vShared.pdf
*/
const seller_name_enc = this._compute_qr_code_field(1, name);
const company_vat_enc = this._compute_qr_code_field(2, vat);
const timestamp_enc = this._compute_qr_code_field(3, date_isostring);
const invoice_total_enc = this._compute_qr_code_field(4, amount_total.toString());
const total_vat_enc = this._compute_qr_code_field(5, amount_tax.toString());
const str_to_encode = seller_name_enc.concat(
company_vat_enc,
timestamp_enc,
invoice_total_enc,
total_vat_enc
);
let binary = "";
for (let i = 0; i < str_to_encode.length; i++) {
binary += String.fromCharCode(str_to_encode[i]);
}
compute_sa_qr_code(name, vat, date_isostring, amount_total, amount_tax) {
/* Generate the qr code for Saudi e-invoicing. Specs are available at the following link at page 23
https://zatca.gov.sa/ar/E-Invoicing/SystemsDevelopers/Documents/20210528_ZATCA_Electronic_Invoice_Security_Features_Implementation_Standards_vShared.pdf
*/
const seller_name_enc = this._compute_qr_code_field(1, name);
const company_vat_enc = this._compute_qr_code_field(2, vat);
const timestamp_enc = this._compute_qr_code_field(3, date_isostring);
const invoice_total_enc = this._compute_qr_code_field(4, amount_total.toString());
const total_vat_enc = this._compute_qr_code_field(5, amount_tax.toString());
const str_to_encode = seller_name_enc.concat(
company_vat_enc,
timestamp_enc,
invoice_total_enc,
total_vat_enc
);
let binary = "";
for (let i = 0; i < str_to_encode.length; i++) {
binary += String.fromCharCode(str_to_encode[i]);
}
return btoa(binary);
}
_compute_qr_code_field(tag, field) {
const textEncoder = new TextEncoder();
const name_byte_array = Array.from(textEncoder.encode(field));
const name_tag_encoding = [tag];
const name_length_encoding = [name_byte_array.length];
return name_tag_encoding.concat(name_length_encoding, name_byte_array);
}
};
Registries.Model.extend(Order, PosL10nSAOrder);
return btoa(binary);
},
_compute_qr_code_field(tag, field) {
const textEncoder = new TextEncoder();
const name_byte_array = Array.from(textEncoder.encode(field));
const name_tag_encoding = [tag];
const name_length_encoding = [name_byte_array.length];
return name_tag_encoding.concat(name_length_encoding, name_byte_array);
},
});
@@ -1,12 +1,14 @@
/** @odoo-module */
import { parse } from "web.field_utils";
import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
import PosComponent from "@point_of_sale/js/PosComponent";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { usePos } from "@point_of_sale/app/pos_store";
import { registry } from "@web/core/registry";
import { Transition } from "@web/core/transition";
import Draggable from "@point_of_sale/js/Misc/Draggable";
import { Draggable } from "@point_of_sale/js/Misc/Draggable";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { OrderImportPopup } from "@point_of_sale/js/Popups/OrderImportPopup";
const { onMounted, onWillUnmount, useRef, useState } = owl;
@@ -21,7 +23,7 @@ export class DebugWidget extends PosComponent {
weightInput: "",
isPaidOrdersReady: false,
isUnpaidOrdersReady: false,
buffer: NumberBuffer.get(),
buffer: numberBuffer.get(),
});
// NOTE: Perhaps this can still be improved.
@@ -50,11 +52,11 @@ export class DebugWidget extends PosComponent {
}
onMounted(() => {
NumberBuffer.on("buffer-update", this, this._onBufferUpdate);
numberBuffer.on("buffer-update", this, this._onBufferUpdate);
});
onWillUnmount(() => {
NumberBuffer.off("buffer-update", this, this._onBufferUpdate);
numberBuffer.off("buffer-update", this, this._onBufferUpdate);
});
}
toggleWidget() {
@@ -81,7 +83,7 @@ export class DebugWidget extends PosComponent {
await this.env.barcode_reader.scan(ean);
}
async deleteOrders() {
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.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."
@@ -93,7 +95,7 @@ export class DebugWidget extends PosComponent {
}
}
async deleteUnpaidOrders() {
const { confirmed } = await this.showPopup("ConfirmPopup", {
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."
@@ -147,7 +149,7 @@ export class DebugWidget extends PosComponent {
const file = event.target.files[0];
if (file) {
const report = this.env.pos.import_orders(await file.text());
await this.showPopup("OrderImportPopup", { report });
await this.showPopup(OrderImportPopup, { report });
}
}
refreshDisplay() {
@@ -3,7 +3,10 @@
import { registry } from "@web/core/registry";
import { odooExceptionTitleMap } from "@web/core/errors/error_dialogs";
import { ConnectionLostError, RPCError } from "@web/core/network/rpc_service";
import { Gui } from "@point_of_sale/js/Gui";
import { Gui, GuiNotReadyError } from "@point_of_sale/js/Gui";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { ErrorTracebackPopup } from "@point_of_sale/js/Popups/ErrorTracebackPopup";
import { OfflineErrorPopup } from "@point_of_sale/js/Popups/OfflineErrorPopup";
export function identifyError(error) {
return error && error.legacy ? error.message : error;
@@ -15,9 +18,9 @@ function rpcErrorHandler(env, error, originalError) {
const { message, data } = error;
if (odooExceptionTitleMap.has(error.exceptionName)) {
const title = odooExceptionTitleMap.get(error.exceptionName).toString();
Gui.showPopup("ErrorPopup", { title, body: data.message });
Gui.showPopup(ErrorPopup, { title, body: data.message });
} else {
Gui.showPopup("ErrorTracebackPopup", {
Gui.showPopup(ErrorTracebackPopup, {
title: message,
body: data.message + "\n" + data.debug + "\n",
});
@@ -30,7 +33,7 @@ registry.category("error_handlers").add("rpcErrorHandler", rpcErrorHandler);
function offlineErrorHandler(env, error, originalError) {
error = identifyError(originalError);
if (error instanceof ConnectionLostError) {
Gui.showPopup("OfflineErrorPopup", {
Gui.showPopup(OfflineErrorPopup, {
title: env._t("Couldn't connect to the server"),
body: env._t(
"The operation couldn't be completed because you are offline. Check your internet connection and try again."
@@ -44,12 +47,12 @@ registry.category("error_handlers").add("offlineErrorHandler", offlineErrorHandl
function defaultErrorHandler(env, error, originalError) {
error = identifyError(originalError);
if (error instanceof Error) {
Gui.showPopup("ErrorTracebackPopup", {
Gui.showPopup(ErrorTracebackPopup, {
title: `${error.name}: ${error.message}`,
body: error.traceback,
});
} else {
Gui.showPopup("ErrorPopup", {
Gui.showPopup(ErrorPopup, {
title: env._t("Unknown Error"),
body: env._t("Unable to show information about this error."),
});
@@ -59,3 +62,12 @@ function defaultErrorHandler(env, error, originalError) {
registry
.category("error_handlers")
.add("defaultErrorHandler", defaultErrorHandler, { sequence: 99 });
function guiNotReadyHandler(env, error, originalError) {
error = identifyError(originalError);
if (error instanceof GuiNotReadyError) {
console.error(originalError);
return true;
}
}
registry.category("error_handlers").add("guiNotReadyHandler", guiNotReadyHandler);
@@ -1,10 +1,30 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { usePos } from "@point_of_sale/app/pos_store";
import { CashierName } from "@point_of_sale/js/ChromeWidgets/CashierName";
import { CashMoveButton } from "@point_of_sale/js/ChromeWidgets/CashMoveButton";
import { CustomerFacingDisplayButton } from "@point_of_sale/js/ChromeWidgets/CustomerFacingDisplayButton";
import { HeaderButton } from "@point_of_sale/js/ChromeWidgets/HeaderButton";
import { ProxyStatus } from "@point_of_sale/js/ChromeWidgets/ProxyStatus";
import { SaleDetailsButton } from "@point_of_sale/js/ChromeWidgets/SaleDetailsButton";
import { SyncNotification } from "@point_of_sale/js/ChromeWidgets/SyncNotification";
import { TicketButton } from "@point_of_sale/js/ChromeWidgets/TicketButton";
export class Navbar extends PosComponent {
static template = "point_of_sale.Navbar";
static components = {
// FIXME POSREF remove some of these components
CashierName,
CashMoveButton,
CustomerFacingDisplayButton,
HeaderButton,
ProxyStatus,
SaleDetailsButton,
SyncNotification,
TicketButton,
};
static props = {
showCashMoveButton: Boolean,
};
+5 -11
View File
@@ -1,16 +1,10 @@
/** @odoo-module */
import { startWebClient } from "@web/start";
import { Chrome } from "@point_of_sale/js/Chrome";
import Registries from "@point_of_sale/js/Registries";
function startPosApp() {
Registries.Component.freeze();
Registries.Model.freeze();
// For consistency's sake, we should trigger"WEB_CLIENT_READY" on the bus when PosApp is mounted
// But we can't since mail and some other poll react on that cue, and we don't want those services started
startWebClient(Registries.Component.get(Chrome));
}
startPosApp();
// FIXME POSREF no we shouldn't but if we did nothing should react, if it does it shouldn't be loaded
// For consistency's sake, we should trigger"WEB_CLIENT_READY" on the bus when PosApp is mounted
// But we can't since mail and some other poll react on that cue, and we don't want those services started
// FIXME POSREF stop using startWebclient: this is not a web client.
startWebClient(Chrome);
+28 -22
View File
@@ -3,10 +3,9 @@
import { loadCSS } from "@web/core/assets";
import { useListener, useBus, useService } from "@web/core/utils/hooks";
import BarcodeParser from "barcodes.BarcodeParser";
import PosComponent from "@point_of_sale/js/PosComponent";
import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
import Registries from "@point_of_sale/js/Registries";
import IndependentToOrderScreen from "@point_of_sale/js/Misc/IndependentToOrderScreen";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen";
import { batched } from "@point_of_sale/js/utils";
import { debounce } from "@web/core/utils/timing";
import { Transition } from "@web/core/transition";
@@ -15,11 +14,17 @@ import { WithEnv } from "@web/core/utils/components";
import { Navbar } from "@point_of_sale/app/navbar/navbar";
// ChromeAdapter imports
import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { PosGlobalState } from "@point_of_sale/js/models";
import { configureGui } from "@point_of_sale/js/Gui";
import { registry } from "@web/core/registry";
import env from "@point_of_sale/js/pos_env";
import { pos_env as env } from "@point_of_sale/js/pos_env";
import { Notification } from "./Notification";
import { PosPopupController } from "./Popups/PosPopupController";
import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup";
import { CashOpeningPopup } from "./Popups/CashOpeningPopup";
import { ConfirmPopup } from "./Popups/ConfirmPopup";
import {
onMounted,
@@ -36,6 +41,7 @@ import { usePos } from "@point_of_sale/app/pos_store";
* Chrome is the root component of the PoS App.
*/
export class Chrome extends PosComponent {
static template = "Chrome"; // FIXME POSREF namespace templates
setup() {
// BEGIN ChromeAdapter
ProductScreen.sortControlButtons();
@@ -43,7 +49,7 @@ export class Chrome extends PosComponent {
// Instantiate PosGlobalState here to ensure that every extension
// (or class overloads) is taken into consideration.
const pos = PosGlobalState.create({ env: markRaw(env) });
const pos = new PosGlobalState({ env: markRaw(env) });
this.batchedCustomerDisplayRender = batched(() => {
reactivePos.send_current_order_to_customer_facing_display();
@@ -95,7 +101,7 @@ export class Chrome extends PosComponent {
useListener("close-notification", this._onCloseNotification);
useListener("connect-to-proxy", this.connect_to_proxy);
useBus(this.env.posbus, "start-cash-control", this.openCashControl);
NumberBuffer.activate();
numberBuffer.activate();
this.state = usePos();
@@ -207,7 +213,7 @@ export class Chrome extends PosComponent {
}
}
return this.showPopup("ErrorTracebackPopup", { title, body, exitButtonIsShown: true });
return this.showPopup(ErrorTracebackPopup, { title, body, exitButtonIsShown: true });
}
registry.category("main_components").add("BlockUI", BlockUiFromRegistry);
@@ -239,10 +245,13 @@ export class Chrome extends PosComponent {
setupBarcodeParser() {
if (!this.env.pos.company.nomenclature_id) {
const errorMessage = this.env._t("The barcode nomenclature setting is not configured. " +
"Make sure to configure it on your Point of Sale configuration settings");
throw new Error(this.env._t("Missing barcode nomenclature"), { cause: { message: errorMessage } });
const errorMessage = this.env._t(
"The barcode nomenclature setting is not configured. " +
"Make sure to configure it on your Point of Sale configuration settings"
);
throw new Error(this.env._t("Missing barcode nomenclature"), {
cause: { message: errorMessage },
});
}
const barcode_parser = new BarcodeParser({
nomenclature_id: this.env.pos.company.nomenclature_id,
@@ -292,7 +301,7 @@ export class Chrome extends PosComponent {
openCashControl() {
if (this.shouldShowCashControl()) {
this.showPopup("CashOpeningPopup", { keepBehind: true });
this.showPopup(CashOpeningPopup, { keepBehind: true });
}
}
@@ -315,7 +324,7 @@ export class Chrome extends PosComponent {
const { name, props, resolve } = event.detail;
this.state.tempScreen = {
name,
component: this.constructor.components[name],
component: registry.category("pos_screens").get(name),
props: { ...props, resolve },
};
}
@@ -323,7 +332,7 @@ export class Chrome extends PosComponent {
this.state.tempScreen = null;
}
__showScreen({ detail: { name, props = {} } }) {
const component = this.constructor.components[name];
const component = registry.category("pos_screens").get(name);
// 1. Set the information of the screen to display.
this.mainScreen.name = name;
this.mainScreen.component = component;
@@ -383,7 +392,7 @@ export class Chrome extends PosComponent {
"not close the session before the issue " +
"has been resolved."
);
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.showPopup(ConfirmPopup, {
title: this.env._t("Offline Orders"),
body: reason,
});
@@ -486,7 +495,6 @@ export class Chrome extends PosComponent {
return this.env.pos && this.env.pos.config && this.env.pos.config.cash_control;
}
}
Chrome.template = "Chrome";
Object.defineProperty(Chrome, "components", {
get() {
return Object.assign(
@@ -495,12 +503,10 @@ Object.defineProperty(Chrome, "components", {
MainComponentsContainer,
WithEnv,
Navbar,
PosPopupController,
Notification,
},
PosComponent.components
);
},
});
Registries.Component.add(Chrome);
export default Chrome;
@@ -1,18 +1,21 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { _t } from "web.core";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { _lt } from "web.core";
import { renderToString } from "@web/core/utils/render";
import { CashMovePopup } from "../Popups/CashMovePopup";
import { ErrorPopup } from "../Popups/ErrorPopup";
const TRANSLATED_CASH_MOVE_TYPE = {
in: _t("in"),
out: _t("out"),
in: _lt("in"),
out: _lt("out"),
};
class CashMoveButton extends PosComponent {
export class CashMoveButton extends PosComponent {
static template = "point_of_sale.CashMoveButton";
async onClick() {
const { confirmed, payload } = await this.showPopup("CashMovePopup");
const { confirmed, payload } = await this.showPopup(CashMovePopup);
if (!confirmed) {
return;
}
@@ -37,7 +40,7 @@ class CashMoveButton extends PosComponent {
});
const printResult = await this.env.proxy.printer.print_receipt(renderedReceipt);
if (!printResult.successful) {
this.showPopup("ErrorPopup", {
this.showPopup(ErrorPopup, {
title: printResult.message.title,
body: printResult.message.body,
});
@@ -55,8 +58,3 @@ class CashMoveButton extends PosComponent {
return result;
}
}
CashMoveButton.template = "point_of_sale.CashMoveButton";
Registries.Component.add(CashMoveButton);
export default CashMoveButton;
@@ -1,10 +1,11 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
// Previously UsernameWidget
class CashierName extends PosComponent {
export class CashierName extends PosComponent {
static template = "CashierName";
get username() {
const { name } = this.env.pos.get_cashier();
return name ? name : "";
@@ -18,8 +19,3 @@ class CashierName extends PosComponent {
return { "not-clickable": true };
}
}
CashierName.template = "CashierName";
Registries.Component.add(CashierName);
export default CashierName;
@@ -1,11 +1,12 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
const { useState } = owl;
class CustomerFacingDisplayButton extends PosComponent {
export class CustomerFacingDisplayButton extends PosComponent {
static template = "CustomerFacingDisplayButton";
setup() {
super.setup();
this.local =
@@ -101,8 +102,3 @@ class CustomerFacingDisplayButton extends PosComponent {
loop();
}
}
CustomerFacingDisplayButton.template = "CustomerFacingDisplayButton";
Registries.Component.add(CustomerFacingDisplayButton);
export default CustomerFacingDisplayButton;
@@ -1,18 +1,15 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ClosePosPopup } from "../Popups/ClosePosPopup";
// Previously HeaderButtonWidget
// This is the close session button
class HeaderButton extends PosComponent {
export class HeaderButton extends PosComponent {
static template = "HeaderButton";
async onClick() {
const info = await this.env.pos.getClosePosInfo();
this.showPopup("ClosePosPopup", { info: info, keepBehind: true });
this.showPopup(ClosePosPopup, { info: info, keepBehind: true });
}
}
HeaderButton.template = "HeaderButton";
Registries.Component.add(HeaderButton);
export default HeaderButton;
@@ -1,12 +1,13 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
const { onMounted, onWillUnmount, useState } = owl;
// Previously ProxyStatusWidget
class ProxyStatus extends PosComponent {
export class ProxyStatus extends PosComponent {
static template = "ProxyStatus";
setup() {
super.setup();
const initialProxyStatus = this.env.proxy.get("status");
@@ -69,8 +70,3 @@ class ProxyStatus extends PosComponent {
}
}
}
ProxyStatus.template = "ProxyStatus";
Registries.Component.add(ProxyStatus);
export default ProxyStatus;
@@ -1,10 +1,12 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { renderToString } from "@web/core/utils/render";
import { ErrorPopup } from "../Popups/ErrorPopup";
export class SaleDetailsButton extends PosComponent {
static template = "SaleDetailsButton";
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
@@ -23,15 +25,10 @@ class SaleDetailsButton extends PosComponent {
);
const printResult = await this.env.proxy.printer.print_receipt(report);
if (!printResult.successful) {
await this.showPopup("ErrorPopup", {
await this.showPopup(ErrorPopup, {
title: printResult.message.title,
body: printResult.message.body,
});
}
}
}
SaleDetailsButton.template = "SaleDetailsButton";
Registries.Component.add(SaleDetailsButton);
export default SaleDetailsButton;
@@ -1,15 +1,11 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class SyncNotification extends PosComponent {
static template = "SyncNotification";
class SyncNotification extends PosComponent {
onClick() {
this.env.pos.push_orders(null, { show_error: true });
}
}
SyncNotification.template = "SyncNotification";
Registries.Component.add(SyncNotification);
export default SyncNotification;
@@ -1,10 +1,11 @@
/** @odoo-module */
import { usePos } from "@point_of_sale/app/pos_store";
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class TicketButton extends PosComponent {
static template = "TicketButton";
class TicketButton extends PosComponent {
setup() {
this.pos = usePos();
}
@@ -26,8 +27,3 @@ class TicketButton extends PosComponent {
}
}
}
TicketButton.template = "TicketButton";
Registries.Component.add(TicketButton);
export default TicketButton;
@@ -1,260 +0,0 @@
/** @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 (const [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 (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;
@@ -1,27 +0,0 @@
/** @odoo-module */
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 (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;
}
}
export default ComponentRegistry;
@@ -1,6 +1,6 @@
/** @odoo-module */
import Registries from "@point_of_sale/js/Registries";
/** @typedef {import("@odoo/owl").Component} Component */
/**
* Component that has this mixin allows the use of `addControlButton`.
@@ -9,7 +9,7 @@ import Registries from "@point_of_sale/js/Registries";
* control buttons can then be rendered in the Component.
* @param {Function} x superclass
*/
const ControlButtonsMixin = (x) => {
export const ControlButtonsMixin = (x) => {
const controlButtonsToPosition = [];
const sortedControlButtons = [];
@@ -19,15 +19,13 @@ const ControlButtonsMixin = (x) => {
.filter((cb) => {
return cb.condition ? cb.condition.bind(this)() : true;
})
.map((cb) =>
Object.assign({}, cb, { component: Registries.Component.get(cb.component) })
);
.map((cb) => Object.assign({}, cb, { component: cb.component }));
}
}
/**
* @param {Object} controlButton
* @param {Function} controlButton.component
* Base class that is added in the Registries.Component.
* @param {new (...args: any[]) => Component} controlButton.component the
* component class
* @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.
@@ -103,5 +101,3 @@ const ControlButtonsMixin = (x) => {
};
return Extended;
};
export default ControlButtonsMixin;
@@ -43,6 +43,7 @@ export const configureGui = ({ component }) => {
]);
};
export class GuiNotReadyError extends Error {}
/**
* Import this and consume like so: `Gui.showPopup(<PopupName>, <props>)`.
* Like you would call `showPopup` in a component.
@@ -57,5 +58,6 @@ export const Gui = new Proxy(config, {
if (availableMethods.has(key) && isMounted) {
return component[key].bind(component);
}
throw new GuiNotReadyError(`Attempted get ${key} on Gui when Chrome is not yet mounted.`);
},
});
@@ -1,7 +1,8 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ConfirmPopup } from "../Popups/ConfirmPopup";
import { ErrorPopup } from "../Popups/ErrorPopup";
const { useRef } = owl;
@@ -11,7 +12,7 @@ const { useRef } = owl;
* `order-receipt` in the template of the Component that extends
* this abstract component.
*/
class AbstractReceiptScreen extends PosComponent {
export class AbstractReceiptScreen extends PosComponent {
setup() {
super.setup();
this.orderReceipt = useRef("order-receipt");
@@ -24,7 +25,7 @@ class AbstractReceiptScreen extends PosComponent {
if (printResult.successful) {
return true;
} else {
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.showPopup(ConfirmPopup, {
title: printResult.message.title,
body: "Do you want to print using the web printer?",
});
@@ -45,7 +46,7 @@ class AbstractReceiptScreen extends PosComponent {
window.print();
return true;
} catch {
await this.showPopup("ErrorPopup", {
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 " +
@@ -56,7 +57,3 @@ class AbstractReceiptScreen extends PosComponent {
}
}
}
Registries.Component.add(AbstractReceiptScreen);
export default AbstractReceiptScreen;
@@ -1,11 +1,7 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
class CurrencyAmount extends PosComponent {}
CurrencyAmount.template = "CurrencyAmount";
Registries.Component.add(CurrencyAmount);
export default CurrencyAmount;
export class CurrencyAmount extends PosComponent {
static template = "CurrencyAmount";
}
@@ -1,8 +1,7 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
const { onMounted, useExternalListener } = owl;
@@ -28,7 +27,9 @@ const { onMounted, useExternalListener } = owl;
*
* @trigger 'drag-end' when dragging ended with payload `{ loc: { top, left } }`
*/
class Draggable extends PosComponent {
export class Draggable extends PosComponent {
static template = "Draggable";
setup() {
super.setup();
this.isDragging = false;
@@ -137,8 +138,3 @@ class Draggable extends PosComponent {
return posTop;
}
}
Draggable.template = "Draggable";
Registries.Component.add(Draggable);
export default Draggable;
@@ -1,13 +1,11 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import { PosComponent } from "@point_of_sale/js/PosComponent";
class IndependentToOrderScreen extends PosComponent {
export class IndependentToOrderScreen extends PosComponent {
close() {
const order = this.env.pos.get_order();
const { name: screenName } = order.get_screen_data();
this.showScreen(screenName);
}
}
export default IndependentToOrderScreen;
@@ -1,9 +1,10 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class MobileOrderWidget extends PosComponent {
static template = "MobileOrderWidget";
class MobileOrderWidget extends PosComponent {
get order() {
return this.env.pos.get_order();
}
@@ -17,9 +18,3 @@ class MobileOrderWidget extends PosComponent {
: 0;
}
}
MobileOrderWidget.template = "MobileOrderWidget";
Registries.Component.add(MobileOrderWidget);
export default MobileOrderWidget;
@@ -317,4 +317,4 @@ class NumberBuffer extends EventBus {
}
}
export default new NumberBuffer();
export const numberBuffer = new NumberBuffer();
@@ -1,8 +1,7 @@
/** @odoo-module */
import { useAutofocus, useListener } from "@web/core/utils/hooks";
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
const { useExternalListener, useState } = owl;
@@ -29,7 +28,9 @@ const { useExternalListener, useState } = owl;
* NOTE: The payload of the emitted event is accessible via the `detail`
* field of the event.
*/
class SearchBar extends PosComponent {
export class SearchBar extends PosComponent {
static template = "point_of_sale.SearchBar";
setup() {
super.setup();
useAutofocus();
@@ -108,7 +109,3 @@ class SearchBar extends PosComponent {
this.state.showSearchFields = false;
}
}
SearchBar.template = "point_of_sale.SearchBar";
Registries.Component.add(SearchBar);
export default SearchBar;
@@ -1,12 +1,13 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
const { onMounted } = owl;
class Notification extends PosComponent {
export class Notification extends PosComponent {
static template = "Notification";
setup() {
super.setup();
useListener("click", this.closeNotification);
@@ -18,8 +19,3 @@ class Notification extends PosComponent {
});
}
}
Notification.template = "Notification";
Registries.Component.add(Notification);
export default Notification;
@@ -1,6 +1,6 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useBus } from "@web/core/utils/hooks";
/**
@@ -22,13 +22,13 @@ import { useBus } from "@web/core/utils/hooks";
* The concrete popup can now be instantiated and be awaited for
* the user's response like so:
* ```js
* const { confirmed, payload } = await this.showPopup('ConcretePopup');
* const { confirmed, payload } = await this.showPopup(ConcretePopup);
* // based on the implementation above,
* // if confirmed, payload = 'result'
* // otherwise, payload = null
* ```
*/
class AbstractAwaitablePopup extends PosComponent {
export class AbstractAwaitablePopup extends PosComponent {
setup() {
super.setup();
if (this.props.confirmKey) {
@@ -58,5 +58,3 @@ class AbstractAwaitablePopup extends PosComponent {
return null;
}
}
export default AbstractAwaitablePopup;
@@ -1,17 +1,14 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
export class AlertPopup extends AbstractAwaitablePopup {}
AlertPopup.template = "AlertPopup";
AlertPopup.defaultProps = {
confirmText: _lt("Ok"),
title: "",
body: "",
cancelKey: false,
};
Registries.Component.add(AlertPopup);
export class AlertPopup extends AbstractAwaitablePopup {
static template = "AlertPopup";
static defaultProps = {
confirmText: _lt("Ok"),
title: "",
body: "",
cancelKey: false,
};
}
@@ -1,12 +1,13 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { parse } from "web.field_utils";
const { useRef, useState, onMounted } = owl;
class CashMovePopup extends AbstractAwaitablePopup {
export class CashMovePopup extends AbstractAwaitablePopup {
static template = "point_of_sale.CashMovePopup";
setup() {
super.setup();
this.state = useState({
@@ -49,7 +50,3 @@ class CashMovePopup extends AbstractAwaitablePopup {
};
}
}
CashMovePopup.template = "point_of_sale.CashMovePopup";
Registries.Component.add(CashMovePopup);
export default CashMovePopup;
@@ -1,11 +1,14 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { MoneyDetailsPopup } from "./MoneyDetailsPopup";
const { useState } = owl;
class CashOpeningPopup extends AbstractAwaitablePopup {
export class CashOpeningPopup extends AbstractAwaitablePopup {
static template = "CashOpeningPopup";
static defaultProps = { cancelKey: false };
setup() {
super.setup();
this.manualInputCashCount = null;
@@ -27,7 +30,7 @@ class CashOpeningPopup extends AbstractAwaitablePopup {
super.confirm();
}
async openDetailsPopup() {
const { confirmed, payload } = await this.showPopup("MoneyDetailsPopup", {
const { confirmed, payload } = await this.showPopup(MoneyDetailsPopup, {
moneyDetails: this.moneyDetails,
total: this.manualInputCashCount ? 0 : this.state.openingCash,
});
@@ -50,9 +53,3 @@ class CashOpeningPopup extends AbstractAwaitablePopup {
}
}
}
CashOpeningPopup.template = "CashOpeningPopup";
CashOpeningPopup.defaultProps = { cancelKey: false };
Registries.Component.add(CashOpeningPopup);
export default CashOpeningPopup;
@@ -1,12 +1,19 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { identifyError } from "@point_of_sale/app/error_handlers/error_handlers";
import { ConnectionLostError } from "@web/core/network/rpc_service";
import { SaleDetailsButton } from "../ChromeWidgets/SaleDetailsButton";
import { ConfirmPopup } from "./ConfirmPopup";
import { ErrorPopup } from "./ErrorPopup";
import { MoneyDetailsPopup } from "./MoneyDetailsPopup";
const { useState } = owl;
class ClosePosPopup extends AbstractAwaitablePopup {
export class ClosePosPopup extends AbstractAwaitablePopup {
static components = { SaleDetailsButton };
static template = "ClosePosPopup";
setup() {
super.setup();
this.manualInputCashCount = false;
@@ -24,7 +31,7 @@ class ClosePosPopup extends AbstractAwaitablePopup {
if (!this.cashControl || !this.hasDifference()) {
this.closeSession();
} else if (this.hasUserAuthority()) {
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.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?"
@@ -34,7 +41,7 @@ class ClosePosPopup extends AbstractAwaitablePopup {
this.closeSession();
}
} else {
await this.showPopup("ConfirmPopup", {
await this.showPopup(ConfirmPopup, {
title: this.env._t("Payments Difference"),
body: _.str.sprintf(
this.env._t(
@@ -54,7 +61,7 @@ class ClosePosPopup extends AbstractAwaitablePopup {
}
}
async openDetailsPopup() {
const { confirmed, payload } = await this.showPopup("MoneyDetailsPopup", {
const { confirmed, payload } = await this.showPopup(MoneyDetailsPopup, {
moneyDetails: this.moneyDetails,
total: this.manualInputCashCount
? 0
@@ -175,7 +182,7 @@ class ClosePosPopup extends AbstractAwaitablePopup {
throw error;
} else {
// FIXME POSREF: why are we catching errors here but not anywhere else in this method?
await this.showPopup("ErrorPopup", {
await this.showPopup(ErrorPopup, {
title: this.env._t("Closing session error"),
body: this.env._t(
"An error has occurred when trying to close the session.\n" +
@@ -210,8 +217,3 @@ class ClosePosPopup extends AbstractAwaitablePopup {
return pm.type == "bank" && pm.number !== 0;
}
}
ClosePosPopup.template = "ClosePosPopup";
Registries.Component.add(ClosePosPopup);
export default ClosePosPopup;
@@ -1,19 +1,18 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
import { Draggable } from "../Misc/Draggable";
// 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);
export default ConfirmPopup;
export class ConfirmPopup extends AbstractAwaitablePopup {
static components = { Draggable };
static template = "ConfirmPopup";
static defaultProps = {
confirmText: _lt("Ok"),
cancelText: _lt("Cancel"),
title: _lt("Confirm ?"),
body: "",
};
}
@@ -1,10 +1,19 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
class ControlButtonPopup extends AbstractAwaitablePopup {
import { Draggable } from "../Misc/Draggable";
export class ControlButtonPopup extends AbstractAwaitablePopup {
static components = { Draggable };
static template = "ControlButtonPopup";
static defaultProps = {
cancelText: _lt("Back"),
controlButtons: [],
confirmKey: false,
};
/**
* @param {Object} props
* @param {string} props.startingValue
@@ -14,13 +23,3 @@ class ControlButtonPopup extends AbstractAwaitablePopup {
this.controlButtons = this.props.controlButtons;
}
}
ControlButtonPopup.template = "ControlButtonPopup";
ControlButtonPopup.defaultProps = {
cancelText: _lt("Back"),
controlButtons: [],
confirmKey: false,
};
Registries.Component.add(ControlButtonPopup);
export default ControlButtonPopup;
@@ -1,12 +1,18 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
const { onMounted, useRef, useState } = owl;
class DatePickerPopup extends AbstractAwaitablePopup {
export class DatePickerPopup extends AbstractAwaitablePopup {
static template = 'DatePickerPopup';
static defaultProps = {
confirmText: _lt('Confirm'),
cancelText: _lt('Discard'),
title: _lt('DatePicker'),
};
setup() {
super.setup();
this.state = useState({shippingDate: this._today()});
@@ -21,13 +27,3 @@ class DatePickerPopup extends AbstractAwaitablePopup {
}
}
DatePickerPopup.template = 'DatePickerPopup';
DatePickerPopup.defaultProps = {
confirmText: _lt('Confirm'),
cancelText: _lt('Discard'),
title: _lt('DatePicker'),
};
Registries.Component.add(DatePickerPopup);
export default DatePickerPopup;
@@ -1,7 +1,6 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
/**
* props {
@@ -10,7 +9,9 @@ import Registries from "@point_of_sale/js/Registries";
* item: object,
* }
*/
class EditListInput extends PosComponent {
export class EditListInput extends PosComponent {
static template = "EditListInput";
onKeyup(event) {
if (event.key === "Enter" && event.target.value.trim() !== "") {
this.props.createNewItem();
@@ -20,8 +21,3 @@ class EditListInput extends PosComponent {
this.props.onInputChange(this.props.item._id, event.target.value);
}
}
EditListInput.template = "EditListInput";
Registries.Component.add(EditListInput);
export default EditListInput;
@@ -1,10 +1,11 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { useAutoFocusToLast } from "@point_of_sale/js/custom_hooks";
import { _lt } from "@web/core/l10n/translation";
import { EditListInput } from "./EditListInput";
const { useState } = owl;
/**
@@ -25,7 +26,7 @@ const { useState } = owl;
*
* // supply the items to the popup and wait for user's response
* // when user pressed `confirm` in the popup, the changes he made will be returned by the showPopup function.
* const { confirmed, payload: newNames } = await this.showPopup('EditListPopup', {
* const { confirmed, payload: newNames } = await this.showPopup(EditListPopup, {
* title: "Can you confirm this item?",
* array: names })
*
@@ -39,7 +40,16 @@ const { useState } = owl;
* }
* ```
*/
class EditListPopup extends AbstractAwaitablePopup {
export class EditListPopup extends AbstractAwaitablePopup {
static components = { EditListInput };
static template = "EditListPopup";
static defaultProps = {
confirmText: _lt("Add"),
cancelText: _lt("Discard"),
array: [],
isSingleItem: false,
};
/**
* @param {String} title required title of popup
* @param {Array} [props.array=[]] the array of { id, text } to be edited or an array of strings
@@ -104,14 +114,3 @@ class EditListPopup extends AbstractAwaitablePopup {
};
}
}
EditListPopup.template = "EditListPopup";
EditListPopup.defaultProps = {
confirmText: _lt("Add"),
cancelText: _lt("Discard"),
array: [],
isSingleItem: false,
};
Registries.Component.add(EditListPopup);
export default EditListPopup;
@@ -1,26 +1,25 @@
/** @odoo-module */
import ErrorPopup from "@point_of_sale/js/Popups/ErrorPopup";
import Registries from "@point_of_sale/js/Registries";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { _lt } from "@web/core/l10n/translation";
import { Draggable } from "../Misc/Draggable";
// formerly ErrorBarcodePopupWidget
class ErrorBarcodePopup extends ErrorPopup {
export class ErrorBarcodePopup extends ErrorPopup {
static components = { Draggable };
static template = "ErrorBarcodePopup";
static 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."
),
};
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."
),
};
Registries.Component.add(ErrorBarcodePopup);
export default ErrorBarcodePopup;
@@ -1,11 +1,18 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
// formerly ErrorPopupWidget
class ErrorPopup extends AbstractAwaitablePopup {
export class ErrorPopup extends AbstractAwaitablePopup {
static template = "ErrorPopup";
static defaultProps = {
confirmText: _lt("Ok"),
title: _lt("Error"),
body: "",
cancelKey: false,
};
setup() {
super.setup();
owl.onMounted(this.onMounted);
@@ -14,14 +21,3 @@ class ErrorPopup extends AbstractAwaitablePopup {
this.playSound("error");
}
}
ErrorPopup.template = "ErrorPopup";
ErrorPopup.defaultProps = {
confirmText: _lt("Ok"),
title: _lt("Error"),
body: "",
cancelKey: false,
};
Registries.Component.add(ErrorPopup);
export default ErrorPopup;
@@ -1,11 +1,22 @@
/** @odoo-module */
import ErrorPopup from "@point_of_sale/js/Popups/ErrorPopup";
import Registries from "@point_of_sale/js/Registries";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { _lt } from "@web/core/l10n/translation";
// formerly ErrorTracebackPopupWidget
class ErrorTracebackPopup extends ErrorPopup {
export class ErrorTracebackPopup extends ErrorPopup {
static template = "ErrorTracebackPopup";
static 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 tracebackUrl() {
const blob = new Blob([this.props.body]);
const URL = window.URL || window.webkitURL;
@@ -27,18 +38,3 @@ class ErrorTracebackPopup extends ErrorPopup {
);
}
}
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);
export default ErrorTracebackPopup;
@@ -1,11 +1,15 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { CurrencyAmount } from "../Misc/CurrencyAmount";
const { useState } = owl;
class MoneyDetailsPopup extends AbstractAwaitablePopup {
export class MoneyDetailsPopup extends AbstractAwaitablePopup {
static components = { CurrencyAmount };
static template = "MoneyDetailsPopup";
setup() {
super.setup();
this.currency = this.env.pos.currency;
@@ -51,8 +55,3 @@ class MoneyDetailsPopup extends AbstractAwaitablePopup {
};
}
}
MoneyDetailsPopup.template = "MoneyDetailsPopup";
Registries.Component.add(MoneyDetailsPopup);
export default MoneyDetailsPopup;
@@ -2,15 +2,27 @@
import core from "web.core";
var _t = core._t;
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import NumberBuffer from "@point_of_sale/js/Misc/NumberBuffer";
import { 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";
import { Draggable } from "../Misc/Draggable";
const { useState } = owl;
// formerly NumberPopupWidget
class NumberPopup extends AbstractAwaitablePopup {
export class NumberPopup extends AbstractAwaitablePopup {
static components = { Draggable };
static template = "NumberPopup";
static defaultProps = {
confirmText: _t("Confirm"),
cancelText: _t("Discard"),
title: _t("Confirm ?"),
body: "",
cheap: false,
startingValue: null,
isPassword: false,
};
/**
* @param {Object} props
* @param {Boolean} props.isPassword Show password popup.
@@ -32,7 +44,7 @@ class NumberPopup extends AbstractAwaitablePopup {
.replace(".", this.decimalSeparator);
}
this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected });
NumberBuffer.use({
numberBuffer.use({
nonKeyboardInputEvent: "numpad-click-input",
triggerAtEnter: "accept-input",
triggerAtEscape: "close-this-popup",
@@ -53,7 +65,7 @@ class NumberPopup extends AbstractAwaitablePopup {
}
}
confirm(event) {
if (NumberBuffer.get()) {
if (numberBuffer.get()) {
super.confirm();
}
}
@@ -61,20 +73,6 @@ class NumberPopup extends AbstractAwaitablePopup {
this.trigger("numpad-click-input", { key });
}
getPayload() {
return NumberBuffer.get();
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);
export default NumberPopup;
@@ -1,28 +1,27 @@
/** @odoo-module */
import ErrorPopup from "@point_of_sale/js/Popups/ErrorPopup";
import Registries from "@point_of_sale/js/Registries";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { _lt } from "@web/core/l10n/translation";
import { Draggable } from "../Misc/Draggable";
/**
* This is a special kind of error popup as it introduces
* an option to not show it again.
*/
class OfflineErrorPopup extends ErrorPopup {
export class OfflineErrorPopup extends ErrorPopup {
static components = { Draggable };
static template = "OfflineErrorPopup";
static dontShow = false;
static defaultProps = {
confirmText: _lt("Ok"),
cancelText: _lt("Cancel"),
title: _lt("Offline Error"),
body: _lt("Either the server is inaccessible or browser is not connected online."),
};
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."),
};
Registries.Component.add(OfflineErrorPopup);
export default OfflineErrorPopup;
@@ -1,11 +1,20 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
import { Draggable } from "../Misc/Draggable";
// formerly OrderImportPopupWidget
class OrderImportPopup extends AbstractAwaitablePopup {
export class OrderImportPopup extends AbstractAwaitablePopup {
static components = { Draggable };
static template = "OrderImportPopup";
static defaultProps = {
confirmText: _lt("Ok"),
cancelKey: false,
body: "",
};
get unpaidSkipped() {
return (
(this.props.report.unpaid_skipped_existing || 0) +
@@ -14,13 +23,3 @@ class OrderImportPopup extends AbstractAwaitablePopup {
}
getPayload() {}
}
OrderImportPopup.template = "OrderImportPopup";
OrderImportPopup.defaultProps = {
confirmText: _lt("Ok"),
cancelKey: false,
body: "",
};
Registries.Component.add(OrderImportPopup);
export default OrderImportPopup;
@@ -1,7 +1,6 @@
/** @odoo-module */
import Registries from "@point_of_sale/js/Registries";
import PosComponent from "@point_of_sale/js/PosComponent";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useBus } from "@web/core/utils/hooks";
/**
@@ -25,7 +24,10 @@ import { useBus } from "@web/core/utils/hooks";
* @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 {
export class PosPopupController extends PosComponent {
static template = "point_of_sale.PosPopupController";
static components = {};
setup() {
super.setup();
useBus(this.env.posbus, "show-popup", this._showPopup);
@@ -33,24 +35,33 @@ class PosPopupController extends PosComponent {
owl.useExternalListener(window, "keyup", this._onWindowKeyup);
this.popups = owl.useState([]);
}
/**
* A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default.
* Also, if it's not the top popup, it is hidden from the view.
* This can be overridden by the default props of the popop component
* and the props used in requesting to show the popup.
*
* @param {AbstractAwaitablePopup} popupComponent
* @param {Object} props
* @returns {BasePopupProps}
*/
_showPopup(event) {
let { id, name, props, resolve } = event.detail;
let { id, component, 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}`,
props: {
keepBehind: false,
cancelKey: "Escape",
confirmKey: "Enter",
// FIXME POSREF assigning the default props by hand defeats the point of default props
...component.defaultProps,
...props,
},
});
}
_closePopup(event) {
@@ -76,28 +87,6 @@ class PosPopupController extends PosComponent {
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.
*/
@@ -120,7 +109,3 @@ class PosPopupController extends PosComponent {
return this.topPopup === popup || popup.props.keepBehind;
}
}
PosPopupController.template = "point_of_sale.PosPopupController";
Registries.Component.add(PosPopupController);
export default PosPopupController;
@@ -1,35 +1,9 @@
/** @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";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
const { useState, useSubEnv } = owl;
export 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) => {
const { value, extra } = attribute_component.getValue();
selected_attributes.push(value);
price_extra += extra;
});
return {
selected_attributes,
price_extra,
};
}
}
ProductConfiguratorPopup.template = "ProductConfiguratorPopup";
Registries.Component.add(ProductConfiguratorPopup);
export class BaseProductAttribute extends PosComponent {
setup() {
super.setup();
@@ -60,6 +34,8 @@ export class BaseProductAttribute extends PosComponent {
}
export class RadioProductAttribute extends BaseProductAttribute {
static template = "RadioProductAttribute";
setup() {
super.setup();
owl.onMounted(this.onMounted);
@@ -72,13 +48,41 @@ export class RadioProductAttribute extends BaseProductAttribute {
$(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 SelectProductAttribute extends BaseProductAttribute {
static template = "SelectProductAttribute";
}
export class ColorProductAttribute extends BaseProductAttribute {}
ColorProductAttribute.template = "ColorProductAttribute";
Registries.Component.add(ColorProductAttribute);
export class ColorProductAttribute extends BaseProductAttribute {
static template = "ColorProductAttribute";
}
export class ProductConfiguratorPopup extends AbstractAwaitablePopup {
static template = "ProductConfiguratorPopup";
static components = {
RadioProductAttribute,
SelectProductAttribute,
ColorProductAttribute
};
setup() {
super.setup();
useSubEnv({ attribute_components: [] });
}
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,
};
}
}
@@ -1,7 +1,6 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
/**
* Props:
@@ -9,7 +8,10 @@ import Registries from "@point_of_sale/js/Registries";
* info: {object of data}
* }
*/
class ProductInfoPopup extends AbstractAwaitablePopup {
export class ProductInfoPopup extends AbstractAwaitablePopup {
static template = "ProductInfoPopup";
static defaultProps = { confirmKey: false };
setup() {
super.setup();
Object.assign(this, this.props.info);
@@ -25,7 +27,3 @@ class ProductInfoPopup extends AbstractAwaitablePopup {
return isAccessibleToEveryUser || isCashierManager;
}
}
ProductInfoPopup.template = "ProductInfoPopup";
ProductInfoPopup.defaultProps = { confirmKey: false };
Registries.Component.add(ProductInfoPopup);
@@ -1,13 +1,24 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
import { Draggable } from "../Misc/Draggable";
const { useState } = owl;
// formerly SelectionPopupWidget
class SelectionPopup extends AbstractAwaitablePopup {
export class SelectionPopup extends AbstractAwaitablePopup {
static components = { Draggable };
static template = "SelectionPopup";
static defaultProps = {
cancelText: _lt("Cancel"),
title: _lt("Select"),
body: "",
list: [],
confirmKey: false,
};
/**
* Value of the `item` key of the selected element in the Selection
* Array is the payload of this popup.
@@ -43,15 +54,3 @@ class SelectionPopup extends AbstractAwaitablePopup {
return selected && selected.item;
}
}
SelectionPopup.template = "SelectionPopup";
SelectionPopup.defaultProps = {
cancelText: _lt("Cancel"),
title: _lt("Select"),
body: "",
list: [],
confirmKey: false,
};
Registries.Component.add(SelectionPopup);
export default SelectionPopup;
@@ -1,15 +1,25 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
import { Draggable } from "../Misc/Draggable";
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 {
export class TextAreaPopup extends AbstractAwaitablePopup {
static components = { Draggable };
static template = "TextAreaPopup";
static defaultProps = {
confirmText: _lt("Add"),
cancelText: _lt("Discard"),
title: "",
body: "",
};
/**
* @param {Object} props
* @param {string} props.startingValue
@@ -27,14 +37,3 @@ class TextAreaPopup extends AbstractAwaitablePopup {
return this.state.inputValue;
}
}
TextAreaPopup.template = "TextAreaPopup";
TextAreaPopup.defaultProps = {
confirmText: _lt("Add"),
cancelText: _lt("Discard"),
title: "",
body: "",
};
Registries.Component.add(TextAreaPopup);
export default TextAreaPopup;
@@ -1,13 +1,22 @@
/** @odoo-module */
import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import Registries from "@point_of_sale/js/Registries";
import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup";
import { _lt } from "@web/core/l10n/translation";
const { onMounted, useRef, useState } = owl;
// formerly TextInputPopupWidget
class TextInputPopup extends AbstractAwaitablePopup {
export class TextInputPopup extends AbstractAwaitablePopup {
static template = "TextInputPopup";
static defaultProps = {
confirmText: _lt("Confirm"),
cancelText: _lt("Discard"),
title: "",
body: "",
startingValue: "",
placeholder: "",
};
setup() {
super.setup();
this.state = useState({ inputValue: this.props.startingValue });
@@ -21,16 +30,3 @@ class TextInputPopup extends AbstractAwaitablePopup {
return this.state.inputValue;
}
}
TextInputPopup.template = "TextInputPopup";
TextInputPopup.defaultProps = {
confirmText: _lt("Confirm"),
cancelText: _lt("Discard"),
title: "",
body: "",
startingValue: "",
placeholder: "",
};
Registries.Component.add(TextInputPopup);
export default TextInputPopup;
@@ -4,7 +4,8 @@ import { LegacyComponent } from "@web/legacy/legacy_component";
let nextId = 0;
class PosComponent extends LegacyComponent {
export class PosComponent extends LegacyComponent {
static components = {};
/**
* This function is available to all Components that inherit this class.
* The goal of this function is to show an awaitable dialog (popup) that
@@ -13,10 +14,7 @@ class PosComponent extends LegacyComponent {
*
* ```
* async getUserName() {
* const userResponse = await this.showPopup(
* 'TextInputPopup',
* { title: 'What is your name?' }
* );
* 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`.
@@ -27,9 +25,9 @@ class PosComponent extends LegacyComponent {
* @param {String} name Name of the popup component
* @param {Object} props Object that will be used to render to popup
*/
showPopup(name, props) {
showPopup(component, props) {
return new Promise((resolve) => {
this.env.posbus.trigger("show-popup", { name, props, resolve, id: nextId++ });
this.env.posbus.trigger("show-popup", { component, props, resolve, id: nextId++ });
});
}
showTempScreen(name, props) {
@@ -61,5 +59,3 @@ class PosComponent extends LegacyComponent {
this.trigger("close-notification");
}
}
export default PosComponent;
@@ -1,27 +0,0 @@
/** @odoo-module */
/**
* This definition contains all the instances of ClassRegistry.
*/
import ComponentRegistry from "@point_of_sale/js/ComponentRegistry";
import ClassRegistry from "@point_of_sale/js/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);
};
}
}
export const Component = new ComponentRegistry();
export const Model = new ModelRegistry();
export default { Component, Model };
@@ -2,12 +2,14 @@
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";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
const { onMounted, onWillUnmount } = owl;
class PartnerDetailsEdit extends PosComponent {
export class PartnerDetailsEdit extends PosComponent {
static template = "PartnerDetailsEdit";
setup() {
super.setup();
this.intFields = ["country_id", "state_id", "property_product_pricelist"];
@@ -56,7 +58,7 @@ class PartnerDetailsEdit extends PosComponent {
}
}
if ((!this.props.partner.name && !processedChanges.name) || processedChanges.name === "") {
return this.showPopup("ErrorPopup", {
return this.showPopup(ErrorPopup, {
title: _t("A Customer Name Is Required"),
});
}
@@ -66,7 +68,7 @@ class PartnerDetailsEdit extends PosComponent {
async uploadImage(event) {
const file = event.target.files[0];
if (!file.type.match(/image.*/)) {
await this.showPopup("ErrorPopup", {
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."
@@ -114,7 +116,7 @@ class PartnerDetailsEdit extends PosComponent {
const img = new Image();
img.addEventListener("load", () => resolve(img));
img.addEventListener("error", () => {
this.showPopup("ErrorPopup", {
this.showPopup(ErrorPopup, {
title: this.env._t("Loading Image Error"),
body: this.env._t("Encountered error when loading image. Please try again."),
});
@@ -124,8 +126,3 @@ class PartnerDetailsEdit extends PosComponent {
});
}
}
PartnerDetailsEdit.template = "PartnerDetailsEdit";
Registries.Component.add(PartnerDetailsEdit);
export default PartnerDetailsEdit;
@@ -1,9 +1,10 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class PartnerLine extends PosComponent {
static template = "PartnerLine";
class PartnerLine extends PosComponent {
get highlight() {
return this._isPartnerSelected ? "highlight" : "";
}
@@ -11,8 +12,3 @@ class PartnerLine extends PosComponent {
return this.props.partner === this.props.selectedPartner;
}
}
PartnerLine.template = "PartnerLine";
Registries.Component.add(PartnerLine);
export default PartnerLine;
@@ -1,11 +1,14 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { registry } from "@web/core/registry";
import { debounce } from "@web/core/utils/timing";
import { useListener } from "@web/core/utils/hooks";
import { PartnerLine } from "./PartnerLine";
import { PartnerDetailsEdit } from "./PartnerDetailsEdit";
const { onWillUnmount, useRef } = owl;
/**
@@ -23,7 +26,10 @@ const { onWillUnmount, useRef } = owl;
*
* @props partner - originally selected partner
*/
class PartnerListScreen extends PosComponent {
export class PartnerListScreen extends PosComponent {
static components = { PartnerDetailsEdit, PartnerLine };
static template = "PartnerListScreen";
setup() {
super.setup();
useListener("click-save", () => this.env.bus.trigger("save-partner"));
@@ -196,8 +202,5 @@ class PartnerListScreen extends PosComponent {
return result;
}
}
PartnerListScreen.template = "PartnerListScreen";
Registries.Component.add(PartnerListScreen);
export default PartnerListScreen;
registry.category("pos_screens").add("PartnerListScreen", PartnerListScreen);
@@ -1,15 +1,11 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class PSNumpadInputButton extends PosComponent {
static template = "PSNumpadInputButton";
class PSNumpadInputButton extends PosComponent {
get _class() {
return this.props.changeClassTo || "input-button number-char";
}
}
PSNumpadInputButton.template = "PSNumpadInputButton";
Registries.Component.add(PSNumpadInputButton);
export default PSNumpadInputButton;
@@ -1,14 +1,30 @@
/** @odoo-module */
import { parse } from "web.field_utils";
import PosComponent from "@point_of_sale/js/PosComponent";
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 { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { registry } from "@web/core/registry";
import { useListener } from "@web/core/utils/hooks";
import Registries from "@point_of_sale/js/Registries";
import utils from "web.utils";
class PaymentScreen extends PosComponent {
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
import { DatePickerPopup } from "@point_of_sale/js/Popups/DatePickerPopup";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { PaymentScreenNumpad } from "./PaymentScreenNumpad";
import { PaymentScreenPaymentLines } from "./PaymentScreenPaymentLines";
import { PaymentScreenStatus } from "./PaymentScreenStatus";
export class PaymentScreen extends PosComponent {
static template = "PaymentScreen";
static components = {
PaymentScreenNumpad,
PaymentScreenPaymentLines,
PaymentScreenStatus,
};
setup() {
super.setup();
useListener("delete-payment-line", this.deletePaymentLine);
@@ -23,14 +39,14 @@ class PaymentScreen extends PosComponent {
this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) =>
this.env.pos.config.payment_method_ids.includes(method.id)
);
NumberBuffer.use(this._getNumberBufferConfig);
numberBuffer.use(this._getNumberBufferConfig);
useErrorHandlers();
this.payment_interface = null;
this.error = false;
}
showMaxValueError() {
this.showPopup("ErrorPopup", {
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."
@@ -83,10 +99,10 @@ class PaymentScreen extends PosComponent {
// original function: click_paymentmethods
const result = this.currentOrder.add_paymentline(paymentMethod);
if (result) {
NumberBuffer.reset();
numberBuffer.reset();
return true;
} else {
this.showPopup("ErrorPopup", {
this.showPopup(ErrorPopup, {
title: this.env._t("Error"),
body: this.env._t("There is already an electronic payment in progress."),
});
@@ -108,10 +124,10 @@ class PaymentScreen extends PosComponent {
) {
return;
}
if (NumberBuffer.get() === null) {
if (numberBuffer.get() === null) {
this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } });
} else {
this.selectedPaymentLine.set_amount(NumberBuffer.getFloat());
this.selectedPaymentLine.set_amount(numberBuffer.getFloat());
}
}
toggleIsToInvoice() {
@@ -128,7 +144,7 @@ class PaymentScreen extends PosComponent {
const change = this.currentOrder.get_change();
const value = tip === 0 && change > 0 ? change : tip;
const { confirmed, payload } = await this.showPopup("NumberPopup", {
const { confirmed, payload } = await this.showPopup(NumberPopup, {
title: tip ? this.env._t("Change Tip") : this.env._t("Add Tip"),
startingValue: value,
isInputSelected: true,
@@ -140,7 +156,7 @@ class PaymentScreen extends PosComponent {
}
async toggleShippingDatePicker() {
if (!this.currentOrder.getShippingDate()) {
const { confirmed, payload: shippingDate } = await this.showPopup("DatePickerPopup", {
const { confirmed, payload: shippingDate } = await this.showPopup(DatePickerPopup, {
title: this.env._t("Select the shipping date"),
});
if (confirmed) {
@@ -164,12 +180,12 @@ class PaymentScreen extends PosComponent {
.send_payment_cancel(this.currentOrder, cid)
.then(function () {
self.currentOrder.remove_paymentline(line);
NumberBuffer.reset();
numberBuffer.reset();
self.render(true);
});
} else if (line.get_payment_status() !== "waitingCancel") {
this.currentOrder.remove_paymentline(line);
NumberBuffer.reset();
numberBuffer.reset();
this.render(true);
}
}
@@ -177,13 +193,13 @@ class PaymentScreen extends PosComponent {
const { cid } = event.detail;
const line = this.paymentLines.find((line) => line.cid === cid);
this.currentOrder.select_paymentline(line);
NumberBuffer.reset();
numberBuffer.reset();
this.render(true);
}
async validateOrder(isForceValidate) {
if (this.env.pos.config.cash_rounding) {
if (!this.env.pos.get_order().check_paymentlines_rounding()) {
this.showPopup("ErrorPopup", {
this.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."
@@ -243,7 +259,7 @@ class PaymentScreen extends PosComponent {
syncOrderResult.map((res) => res.id)
);
if (!postPushResult) {
this.showPopup("ErrorPopup", {
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."
@@ -275,7 +291,7 @@ class PaymentScreen extends PosComponent {
// Ask the user to sync the remaining unsynced orders.
if (!hasError && syncOrderResult && this.env.pos.db.get_orders().length) {
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.showPopup(ConfirmPopup, {
title: this.env._t("Remaining unsynced orders"),
body: this.env._t(
"There are unsynced orders. Do you want to sync these orders?"
@@ -294,18 +310,19 @@ class PaymentScreen extends PosComponent {
return !this.error ? "ReceiptScreen" : "ProductScreen";
}
paymentMethodImage(id) {
if (this.paymentMethod.image)
if (this.paymentMethod.image) {
return `/web/image/pos.payment.method/${id}/image`;
else if (this.paymentMethod.type === "cash")
} else if (this.paymentMethod.type === "cash") {
return "/point_of_sale/static/src/img/money.png";
else if(this.paymentMethod.type === "pay_later")
} else if (this.paymentMethod.type === "pay_later") {
return "/point_of_sale/static/src/img/pay-later.png";
else
} else {
return "/point_of_sale/static/src/img/card-bank.png";
}
}
async _isOrderValid(isForceValidate) {
if (this.currentOrder.get_orderlines().length === 0 && this.currentOrder.is_to_invoice()) {
this.showPopup("ErrorPopup", {
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."
@@ -319,7 +336,7 @@ class PaymentScreen extends PosComponent {
);
if (splitPayments.length && !this.currentOrder.get_partner()) {
const paymentMethod = splitPayments[0].payment_method;
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.showPopup(ConfirmPopup, {
title: this.env._t("Customer Required"),
body: _.str.sprintf(
this.env._t("Customer is required for %s payment method."),
@@ -336,7 +353,7 @@ class PaymentScreen extends PosComponent {
(this.currentOrder.is_to_invoice() || this.currentOrder.getShippingDate()) &&
!this.currentOrder.get_partner()
) {
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.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."
@@ -353,7 +370,7 @@ class PaymentScreen extends PosComponent {
this.currentOrder.getShippingDate() &&
!(partner.name && partner.street && partner.city && partner.country_id)
) {
this.showPopup("ErrorPopup", {
this.showPopup(ErrorPopup, {
title: this.env._t("Incorrect address for shipping"),
body: this.env._t("The selected customer needs an address."),
});
@@ -374,7 +391,7 @@ class PaymentScreen extends PosComponent {
if (this.currentOrder.has_not_valid_rounding()) {
var line = this.currentOrder.has_not_valid_rounding();
this.showPopup("ErrorPopup", {
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."
@@ -396,7 +413,7 @@ class PaymentScreen extends PosComponent {
cash = cash || this.env.pos.payment_methods[i].is_cash_count;
}
if (!cash) {
this.showPopup("ErrorPopup", {
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"
@@ -412,7 +429,7 @@ class PaymentScreen extends PosComponent {
this.currentOrder.get_total_with_tax() > 0 &&
this.currentOrder.get_total_with_tax() * 1000 < this.currentOrder.get_total_paid()
) {
this.showPopup("ConfirmPopup", {
this.showPopup(ConfirmPopup, {
title: this.env._t("Please Confirm Large Amount"),
body:
this.env._t("Are you sure that the customer wants to pay") +
@@ -500,8 +517,5 @@ class PaymentScreen extends PosComponent {
line.set_payment_status("done");
}
}
PaymentScreen.template = "PaymentScreen";
Registries.Component.add(PaymentScreen);
export default PaymentScreen;
registry.category("pos_screens").add("PaymentScreen", PaymentScreen);
@@ -1,16 +1,15 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { PSNumpadInputButton } from "./PSNumpadInputButton";
export class PaymentScreenNumpad extends PosComponent {
static components = { PSNumpadInputButton };
static template = "PaymentScreenNumpad";
class PaymentScreenNumpad extends PosComponent {
setup() {
super.setup();
this.decimalPoint = this.env._t.database.parameters.decimal_point;
}
}
PaymentScreenNumpad.template = "PaymentScreenNumpad";
Registries.Component.add(PaymentScreenNumpad);
export default PaymentScreenNumpad;
@@ -1,9 +1,10 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class PaymentScreenPaymentLines extends PosComponent {
static template = "PaymentScreenPaymentLines";
class PaymentScreenPaymentLines extends PosComponent {
formatLineAmount(paymentline) {
return this.env.pos.format_currency_no_symbol(paymentline.get_amount());
}
@@ -14,8 +15,3 @@ class PaymentScreenPaymentLines extends PosComponent {
return {};
}
}
PaymentScreenPaymentLines.template = "PaymentScreenPaymentLines";
Registries.Component.add(PaymentScreenPaymentLines);
export default PaymentScreenPaymentLines;
@@ -1,9 +1,10 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class PaymentScreenStatus extends PosComponent {
static template = "PaymentScreenStatus";
class PaymentScreenStatus extends PosComponent {
get changeText() {
return this.env.pos.format_currency(this.props.order.get_change());
}
@@ -18,8 +19,3 @@ class PaymentScreenStatus extends PosComponent {
);
}
}
PaymentScreenStatus.template = "PaymentScreenStatus";
Registries.Component.add(PaymentScreenStatus);
export default PaymentScreenStatus;
@@ -1,23 +1,19 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
/**
* @props partner
* @emits click-partner
* @emits click-pay
*/
class ActionpadWidget extends PosComponent {
export class ActionpadWidget extends PosComponent {
static template = "ActionpadWidget";
static defaultProps = {
isActionButtonHighlighted: false,
};
get isLongName() {
return this.props.partner && this.props.partner.name.length > 10;
}
}
ActionpadWidget.template = "ActionpadWidget";
ActionpadWidget.defaultProps = {
isActionButtonHighlighted: false,
};
Registries.Component.add(ActionpadWidget);
export default ActionpadWidget;
@@ -1,16 +1,12 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class CategoryButton extends PosComponent {
static template = "CategoryButton";
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";
Registries.Component.add(CategoryButton);
export default CategoryButton;
@@ -1,11 +1,13 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
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 { TextAreaPopup } from "@point_of_sale/js/Popups/TextAreaPopup";
export class OrderlineCustomerNoteButton extends PosComponent {
static template = "OrderlineCustomerNoteButton";
class OrderlineCustomerNoteButton extends PosComponent {
setup() {
super.setup();
useListener("click", this.onClick);
@@ -16,7 +18,7 @@ class OrderlineCustomerNoteButton extends PosComponent {
return;
}
const { confirmed, payload: inputNote } = await this.showPopup("TextAreaPopup", {
const { confirmed, payload: inputNote } = await this.showPopup(TextAreaPopup, {
startingValue: selectedOrderline.get_customer_note(),
title: this.env._t("Add Customer Note"),
});
@@ -26,12 +28,7 @@ class OrderlineCustomerNoteButton extends PosComponent {
}
}
}
OrderlineCustomerNoteButton.template = "OrderlineCustomerNoteButton";
ProductScreen.addControlButton({
component: OrderlineCustomerNoteButton,
});
Registries.Component.add(OrderlineCustomerNoteButton);
export default OrderlineCustomerNoteButton;
@@ -1,11 +1,13 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
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 { ProductInfoPopup } from "@point_of_sale/js/Popups/ProductInfoPopup";
export class ProductInfoButton extends PosComponent {
static template = "ProductInfoButton";
class ProductInfoButton extends PosComponent {
setup() {
super.setup();
useListener("click", this.onClick);
@@ -16,17 +18,12 @@ class ProductInfoButton extends PosComponent {
const product = orderline.get_product();
const quantity = orderline.get_quantity();
const info = await this.env.pos.getProductInfo(product, quantity);
this.showPopup("ProductInfoPopup", { info: info, product: product });
this.showPopup(ProductInfoPopup, { info: info, product: product });
}
}
}
ProductInfoButton.template = "ProductInfoButton";
ProductScreen.addControlButton({
component: ProductInfoButton,
position: ["before", "SetFiscalPositionButton"],
});
Registries.Component.add(ProductInfoButton);
export default ProductInfoButton;
@@ -1,11 +1,12 @@
/** @odoo-module */
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 { PosComponent } from "@point_of_sale/js/PosComponent";
import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
import { useListener } from "@web/core/utils/hooks";
class RefundButton extends PosComponent {
export class RefundButton extends PosComponent {
static template = "point_of_sale.RefundButton";
setup() {
super.setup();
useListener("click", this._onClick);
@@ -19,7 +20,6 @@ class RefundButton extends PosComponent {
});
}
}
RefundButton.template = "point_of_sale.RefundButton";
ProductScreen.addControlButton({
component: RefundButton,
@@ -27,7 +27,3 @@ ProductScreen.addControlButton({
return true;
},
});
Registries.Component.add(RefundButton);
export default RefundButton;
@@ -1,11 +1,13 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
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 { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
export class SetFiscalPositionButton extends PosComponent {
static template = "SetFiscalPositionButton";
class SetFiscalPositionButton extends PosComponent {
setup() {
super.setup();
useListener("click", this.onClick);
@@ -38,7 +40,7 @@ class SetFiscalPositionButton extends PosComponent {
});
}
const { confirmed, payload: selectedFiscalPosition } = await this.showPopup(
"SelectionPopup",
SelectionPopup,
{
title: this.env._t("Select Fiscal Position"),
list: fiscalPosList,
@@ -54,7 +56,6 @@ class SetFiscalPositionButton extends PosComponent {
}
}
}
SetFiscalPositionButton.template = "SetFiscalPositionButton";
ProductScreen.addControlButton({
component: SetFiscalPositionButton,
@@ -63,7 +64,3 @@ ProductScreen.addControlButton({
},
position: ["before", "SetPricelistButton"],
});
Registries.Component.add(SetFiscalPositionButton);
export default SetFiscalPositionButton;
@@ -1,11 +1,13 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import ProductScreen from "@point_of_sale/js/Screens/ProductScreen/ProductScreen";
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 { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup";
export class SetPricelistButton extends PosComponent {
static template = "SetPricelistButton";
class SetPricelistButton extends PosComponent {
setup() {
super.setup();
useListener("click", this.onClick);
@@ -24,21 +26,21 @@ class SetPricelistButton extends PosComponent {
const selectionList = this.env.pos.pricelists.map((pricelist) => ({
id: pricelist.id,
label: pricelist.name,
isSelected: this.currentOrder.pricelist
&& pricelist.id === this.currentOrder.pricelist.id,
isSelected:
this.currentOrder.pricelist && pricelist.id === this.currentOrder.pricelist.id,
item: pricelist,
}));
if (!this.env.pos.default_pricelist) {
selectionList.push({
id: null,
label: this.env._t('Default Price'),
label: this.env._t("Default Price"),
isSelected: !this.currentOrder.pricelist,
item: null,
})
});
}
const { confirmed, payload: selectedPricelist } = await this.showPopup("SelectionPopup", {
const { confirmed, payload: selectedPricelist } = await this.showPopup(SelectionPopup, {
title: this.env._t("Select the pricelist"),
list: selectionList,
});
@@ -48,7 +50,6 @@ class SetPricelistButton extends PosComponent {
}
}
}
SetPricelistButton.template = "SetPricelistButton";
ProductScreen.addControlButton({
component: SetPricelistButton,
@@ -56,7 +57,3 @@ ProductScreen.addControlButton({
return this.env.pos.config.use_pricelist && this.env.pos.pricelists.length > 0;
},
});
Registries.Component.add(SetPricelistButton);
export default SetPricelistButton;
@@ -1,7 +1,6 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
/**
* @prop {'quantity' | 'price' | 'discount'} activeMode
@@ -10,7 +9,13 @@ import Registries from "@point_of_sale/js/Registries";
* @event set-numpad-mode - triggered when mode button is clicked
* @event numpad-click-input - triggered when numpad button is clicked
*/
class NumpadWidget extends PosComponent {
export class NumpadWidget extends PosComponent {
static template = "NumpadWidget";
static defaultProps = {
disabledModes: [],
disableSign: false,
};
get hasPriceControlRights() {
return (
this.env.pos.cashierHasPriceControlRights() &&
@@ -38,12 +43,3 @@ class NumpadWidget extends PosComponent {
return this.env._t.database.parameters.decimal_point;
}
}
NumpadWidget.template = "NumpadWidget";
NumpadWidget.defaultProps = {
disabledModes: [],
disableSign: false,
};
Registries.Component.add(NumpadWidget);
export default NumpadWidget;
@@ -1,10 +1,11 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { float_is_zero } from "web.utils";
class OrderSummary extends PosComponent {
export class OrderSummary extends PosComponent {
static template = "OrderSummary";
getTotal() {
return this.env.pos.format_currency(this.props.order.get_total_with_tax());
}
@@ -18,8 +19,3 @@ class OrderSummary extends PosComponent {
};
}
}
OrderSummary.template = "OrderSummary";
Registries.Component.add(OrderSummary);
export default OrderSummary;
@@ -1,12 +1,18 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup";
import { Orderline } from "./Orderline";
import { OrderSummary } from "./OrderSummary";
const { useEffect, useRef } = owl;
class OrderWidget extends PosComponent {
export class OrderWidget extends PosComponent {
static components = { Orderline, OrderSummary };
static template = "OrderWidget";
setup() {
super.setup();
useListener("select-line", this._selectLine);
@@ -42,7 +48,7 @@ class OrderWidget extends PosComponent {
const orderline = event.detail.orderline;
const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot();
const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot);
const { confirmed, payload } = await this.showPopup("EditListPopup", {
const { confirmed, payload } = await this.showPopup(EditListPopup, {
title: this.env._t("Lot/Serial Number(s) Required"),
name: orderline.product.display_name,
isSingleItem: isAllowOnlyOneLot,
@@ -62,8 +68,3 @@ class OrderWidget extends PosComponent {
this.order.select_orderline(event.detail.orderline);
}
}
OrderWidget.template = "OrderWidget";
Registries.Component.add(OrderWidget);
export default OrderWidget;
@@ -1,9 +1,10 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class Orderline extends PosComponent {
static template = "Orderline";
class Orderline extends PosComponent {
selectLine() {
this.trigger("select-line", { orderline: this.props.line });
}
@@ -19,8 +20,3 @@ class Orderline extends PosComponent {
return this.props.line.get_customer_note();
}
}
Orderline.template = "Orderline";
Registries.Component.add(Orderline);
export default Orderline;
@@ -1,9 +1,10 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class ProductItem extends PosComponent {
static template = "ProductItem";
class ProductItem extends PosComponent {
/**
* For accessibility, pressing <space> should be like clicking the product.
* <enter> is not considered because it conflicts with the barcode.
@@ -40,8 +41,3 @@ class ProductItem extends PosComponent {
}
}
}
ProductItem.template = "ProductItem";
Registries.Component.add(ProductItem);
export default ProductItem;
@@ -1,16 +1,39 @@
/** @odoo-module */
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 { PosComponent } from "@point_of_sale/js/PosComponent";
import { ControlButtonsMixin } from "@point_of_sale/js/ControlButtonsMixin";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { registry } from "@web/core/registry";
import { useListener } from "@web/core/utils/hooks";
import Registries from "@point_of_sale/js/Registries";
import { useBarcodeReader } from "@point_of_sale/js/custom_hooks";
import { parse } from "web.field_utils";
import { ProductConfiguratorPopup } from "@point_of_sale/js/Popups/ProductConfiguratorPopup";
import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup";
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { ErrorBarcodePopup } from "@point_of_sale/js/Popups/ErrorBarcodePopup";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { ControlButtonPopup } from "@point_of_sale/js/Popups/ControlButtonPopup";
import { ActionpadWidget } from "./ActionpadWidget";
import { MobileOrderWidget } from "../../Misc/MobileOrderWidget";
import { NumpadWidget } from "./NumpadWidget";
import { OrderWidget } from "./OrderWidget";
import { ProductsWidget } from "./ProductsWidget";
const { onMounted, useState } = owl;
class ProductScreen extends ControlButtonsMixin(PosComponent) {
export class ProductScreen extends ControlButtonsMixin(PosComponent) {
static template = "ProductScreen";
static components = {
ActionpadWidget,
MobileOrderWidget,
NumpadWidget,
OrderWidget,
ProductsWidget,
};
setup() {
super.setup();
useListener("update-selected-orderline", this._updateSelectedOrderline);
@@ -27,16 +50,16 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
discount: this._barcodeDiscountAction,
error: this._barcodeErrorAction,
});
NumberBuffer.use({
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.
// Call `reset` when the `onMounted` callback in `numberBuffer.use` is done.
// We don't do this in the `mounted` lifecycle method because it is called before
// the callbacks in `onMounted` hook.
onMounted(() => NumberBuffer.reset());
onMounted(() => numberBuffer.reset());
this.state = useState({
mobile_pane: this.props.mobile_pane || "right",
});
@@ -67,7 +90,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
product.attribute_line_ids,
(id) => this.env.pos.attributes_by_ptal_id[id]
).filter((attr) => attr !== undefined);
const { confirmed, payload } = await this.showPopup("ProductConfiguratorPopup", {
const { confirmed, payload } = await this.showPopup(ProductConfiguratorPopup, {
product: product,
attributes: attributes,
});
@@ -100,7 +123,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
packLotLinesToEdit = [];
}
}
const { confirmed, payload } = await this.showPopup("EditListPopup", {
const { confirmed, payload } = await this.showPopup(EditListPopup, {
title: this.env._t("Lot/Serial Number(s) Required"),
name: product.display_name,
isSingleItem: isAllowOnlyOneLot,
@@ -161,16 +184,16 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
}
// Add the product after having the extra information.
await this._addProduct(product, options);
NumberBuffer.reset();
numberBuffer.reset();
}
_setNumpadMode(event) {
const { mode } = event.detail;
NumberBuffer.capture();
NumberBuffer.reset();
numberBuffer.capture();
numberBuffer.reset();
this.env.pos.numpadMode = mode;
}
_selectLine() {
NumberBuffer.reset();
numberBuffer.reset();
}
async _updateSelectedOrderline(event) {
if (this.env.pos.numpadMode === "quantity" && this.env.pos.disallowLineQuantityChange()) {
@@ -187,7 +210,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
.get_quantity();
if (selectedLine.noDecrease) {
this.showPopup("ErrorPopup", {
this.showPopup(ErrorPopup, {
title: this.env._t("Invalid action"),
body: this.env._t("You are not allowed to change this quantity"),
});
@@ -206,7 +229,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
const val = buffer === null ? "remove" : buffer;
this._setValue(val);
if (val == "remove") {
NumberBuffer.reset();
numberBuffer.reset();
this.env.pos.numpadMode = "quantity";
}
}
@@ -216,7 +239,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
if (this.env.pos.numpadMode === "quantity") {
const result = this.currentOrder.get_selected_orderline().set_quantity(val);
if (!result) {
NumberBuffer.reset();
numberBuffer.reset();
}
} else if (this.env.pos.numpadMode === "discount") {
this.currentOrder.get_selected_orderline().set_discount(val);
@@ -296,7 +319,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
// Why? Because once we start declaring barcode actions in different
// screens, these methods will also be declared over and over.
_barcodeErrorAction(code) {
this.showPopup("ErrorBarcodePopup", { code: this._codeRepr(code) });
this.showPopup(ErrorBarcodePopup, { code: this._codeRepr(code) });
}
_codeRepr(code) {
if (code.code.length > 32) {
@@ -306,7 +329,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
}
}
async _displayAllControlPopup() {
await this.showPopup("ControlButtonPopup", {
await this.showPopup(ControlButtonPopup, {
controlButtons: this.controlButtons,
});
}
@@ -316,7 +339,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
*/
async _onScaleNotAvailable() {}
async _showDecreaseQuantityPopup() {
const { confirmed, payload: inputNumber } = await this.showPopup("NumberPopup", {
const { confirmed, payload: inputNumber } = await this.showPopup(NumberPopup, {
startingValue: 0,
title: this.env._t("Set the new quantity"),
});
@@ -347,7 +370,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
// IMPROVEMENT: This code snippet is very similar to selectPartner of PaymentScreen.
const currentPartner = this.currentOrder.get_partner();
if (currentPartner && this.currentOrder.getHasRefundLines()) {
this.showPopup("ErrorPopup", {
this.showPopup(ErrorPopup, {
title: this.env._t("Can't change customer"),
body: _.str.sprintf(
this.env._t(
@@ -377,7 +400,7 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
(this.env.pos.picking_type.use_create_lots ||
this.env.pos.picking_type.use_existing_lots)
) {
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.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?"
@@ -396,8 +419,5 @@ class ProductScreen extends ControlButtonsMixin(PosComponent) {
this.state.mobile_pane = this.state.mobile_pane === "left" ? "right" : "left";
}
}
ProductScreen.template = "ProductScreen";
Registries.Component.add(ProductScreen);
export default ProductScreen;
registry.category("pos_screens").add("ProductScreen", ProductScreen);
@@ -1,13 +1,18 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { useListener } from "@web/core/utils/hooks";
import Registries from "@point_of_sale/js/Registries";
import { usePos } from "@point_of_sale/app/pos_store";
import { ProductItem } from "./ProductItem";
import { ProductsWidgetControlPanel } from "./ProductsWidgetControlPanel";
const { useState } = owl;
class ProductsWidget extends PosComponent {
export class ProductsWidget extends PosComponent {
static components = { ProductItem, ProductsWidgetControlPanel };
static template = "ProductsWidget";
/**
* @param {Object} props
* @param {number?} props.startCategoryId
@@ -78,8 +83,3 @@ class ProductsWidget extends PosComponent {
this.trigger("switch-category", 0);
}
}
ProductsWidget.template = "ProductsWidget";
Registries.Component.add(ProductsWidget);
export default ProductsWidget;
@@ -1,13 +1,17 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { debounce } from "@web/core/utils/timing";
import { usePos } from "@point_of_sale/app/pos_store";
import { CategoryButton } from "./CategoryButton";
import { onMounted, onWillUnmount, useState } from "@odoo/owl";
class ProductsWidgetControlPanel extends PosComponent {
export class ProductsWidgetControlPanel extends PosComponent {
static components = { CategoryButton };
static template = "ProductsWidgetControlPanel";
setup() {
super.setup();
this.pos = usePos();
@@ -88,8 +92,3 @@ class ProductsWidgetControlPanel extends PosComponent {
return ProductIds;
}
}
ProductsWidgetControlPanel.template = "ProductsWidgetControlPanel";
Registries.Component.add(ProductsWidgetControlPanel);
export default ProductsWidgetControlPanel;
@@ -1,11 +1,15 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { WrappedProductNameLines } from "./WrappedProductNameLines";
const { onWillUpdateProps } = owl;
class OrderReceipt extends PosComponent {
export class OrderReceipt extends PosComponent {
static components = { WrappedProductNameLines };
static template = "OrderReceipt";
setup() {
super.setup();
this._receiptEnv = this.props.order.getOrderReceiptEnv();
@@ -41,8 +45,3 @@ class OrderReceipt extends PosComponent {
);
}
}
OrderReceipt.template = "OrderReceipt";
Registries.Component.add(OrderReceipt);
export default OrderReceipt;
@@ -3,150 +3,149 @@
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";
import { AbstractReceiptScreen } from "@point_of_sale/js/Misc/AbstractReceiptScreen";
import { registry } from "@web/core/registry";
import { OrderReceipt } from "./OrderReceipt";
import { onMounted, useRef, status } from "@odoo/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;
export class ReceiptScreen extends AbstractReceiptScreen {
static template = "ReceiptScreen";
static components = { OrderReceipt };
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();
}
await this.handleAutoPrint();
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") {
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
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."
);
}
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],
});
}
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();
}
}
}
ReceiptScreen.template = "ReceiptScreen";
return ReceiptScreen;
};
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],
});
}
}
Registries.Component.addByExtending(ReceiptScreen, AbstractReceiptScreen);
export default ReceiptScreen;
registry.category("pos_screens").add("ReceiptScreen", ReceiptScreen);
@@ -1,11 +1,7 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
class WrappedProductNameLines extends PosComponent {}
WrappedProductNameLines.template = "WrappedProductNameLines";
Registries.Component.add(WrappedProductNameLines);
export default WrappedProductNameLines;
export class WrappedProductNameLines extends PosComponent {
static template = "WrappedProductNameLines";
}
@@ -1,12 +1,14 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
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";
import { registry } from "@web/core/registry";
const { onMounted, onWillUnmount, useExternalListener, useState } = owl;
class ScaleScreen extends PosComponent {
export class ScaleScreen extends PosComponent {
static template = "ScaleScreen";
/**
* @param {Object} props
* @param {Object} props.product The product to weight.
@@ -95,8 +97,5 @@ class ScaleScreen extends PosComponent {
: "";
}
}
ScaleScreen.template = "ScaleScreen";
Registries.Component.add(ScaleScreen);
export default ScaleScreen;
registry.category("pos_screens").add("ScaleScreen", ScaleScreen);
@@ -1,10 +1,13 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
export class InvoiceButton extends PosComponent {
static template = "InvoiceButton";
class InvoiceButton extends PosComponent {
setup() {
super.setup();
useListener("click", this._onClick);
@@ -42,7 +45,7 @@ class InvoiceButton extends PosComponent {
throw error;
} else {
// NOTE: error here is most probably undefined
this.showPopup("ErrorPopup", {
this.showPopup(ErrorPopup, {
title: this.env._t("Network Error"),
body: this.env._t("Unable to download invoice."),
});
@@ -66,7 +69,7 @@ class InvoiceButton extends PosComponent {
// Part 1: Handle missing partner.
// Write to pos.order the selected partner.
if (!order.get_partner()) {
const { confirmed: confirmedPopup } = await this.showPopup("ConfirmPopup", {
const { confirmed: confirmedPopup } = await this.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?"),
});
@@ -115,7 +118,3 @@ class InvoiceButton extends PosComponent {
}
}
}
InvoiceButton.template = "InvoiceButton";
Registries.Component.add(InvoiceButton);
export default InvoiceButton;
@@ -1,10 +1,11 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
export class ReprintReceiptButton extends PosComponent {
static template = "ReprintReceiptButton";
class ReprintReceiptButton extends PosComponent {
setup() {
super.setup();
useListener("click", this._onClick);
@@ -16,7 +17,3 @@ class ReprintReceiptButton extends PosComponent {
this.showScreen("ReprintReceiptScreen", { order: this.props.order });
}
}
ReprintReceiptButton.template = "ReprintReceiptButton";
Registries.Component.add(ReprintReceiptButton);
export default ReprintReceiptButton;
@@ -1,12 +1,15 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { OrderlineDetails } from "./OrderlineDetails";
/**
* @props {models.Order} order
*/
class OrderDetails extends PosComponent {
export class OrderDetails extends PosComponent {
static components = { OrderlineDetails };
static template = "OrderDetails";
get order() {
return this.props.order;
}
@@ -20,8 +23,3 @@ class OrderDetails extends PosComponent {
return this.env.pos.format_currency(this.order ? this.order.get_total_tax() : 0);
}
}
OrderDetails.template = "OrderDetails";
Registries.Component.add(OrderDetails);
export default OrderDetails;
@@ -1,14 +1,15 @@
/** @odoo-module */
import PosComponent from "@point_of_sale/js/PosComponent";
import Registries from "@point_of_sale/js/Registries";
import { PosComponent } from "@point_of_sale/js/PosComponent";
import { format } from "web.field_utils";
import { round_precision as round_pr } from "web.utils";
/**
* @props {pos.order.line} line
*/
class OrderlineDetails extends PosComponent {
export class OrderlineDetails extends PosComponent {
static template = "OrderlineDetails";
get line() {
const line = this.props.line;
const formatQty = (line) => {
@@ -72,8 +73,3 @@ class OrderlineDetails extends PosComponent {
return _.str.sprintf(this.env._t("To Refund: %s"), this.getFormattedToRefundQty());
}
}
OrderlineDetails.template = "OrderlineDetails";
Registries.Component.add(OrderlineDetails);
export default OrderlineDetails;
@@ -1,35 +1,33 @@
/** @odoo-module */
import AbstractReceiptScreen from "@point_of_sale/js/Misc/AbstractReceiptScreen";
import Registries from "@point_of_sale/js/Registries";
import { AbstractReceiptScreen } from "@point_of_sale/js/Misc/AbstractReceiptScreen";
import { registry } from "@web/core/registry";
import { OrderReceipt } from "../ReceiptScreen/OrderReceipt";
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 });
}
export class ReprintReceiptScreen extends AbstractReceiptScreen {
static template = "ReprintReceiptScreen";
static components = { OrderReceipt };
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();
}
}
export default ReprintReceiptScreen;
registry.category("pos_screens").add("ReprintReceiptScreen", ReprintReceiptScreen);
@@ -1,15 +1,44 @@
/** @odoo-module */
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 { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen";
import { numberBuffer } from "@point_of_sale/js/Misc/NumberBuffer";
import { registry } from "@web/core/registry";
import { useListener } from "@web/core/utils/hooks";
import { parse } from "web.field_utils";
import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { ActionpadWidget } from "../ProductScreen/ActionpadWidget";
import { InvoiceButton } from "../TicketScreen/ControlButtons/InvoiceButton";
import { NumpadWidget } from "../ProductScreen/NumpadWidget";
import { OrderDetails } from "../TicketScreen/OrderDetails";
import { ReprintReceiptButton } from "./ControlButtons/ReprintReceiptButton";
import { SearchBar } from "../../Misc/SearchBar";
const { onMounted, onWillUnmount, useState } = owl;
class TicketScreen extends IndependentToOrderScreen {
export class TicketScreen extends IndependentToOrderScreen {
static template = "TicketScreen";
static components = {
ActionpadWidget,
InvoiceButton,
NumpadWidget,
OrderDetails,
ReprintReceiptButton,
SearchBar,
};
static 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: {},
};
setup() {
super.setup();
useListener("close-screen", this._onCloseScreen);
@@ -25,7 +54,7 @@ class TicketScreen extends IndependentToOrderScreen {
useListener("click-refund-order-uid", this._onClickRefundOrderUid);
useListener("update-selected-orderline", this._onUpdateSelectedOrderline);
useListener("do-refund", this._onDoRefund);
NumberBuffer.use({
numberBuffer.use({
nonKeyboardInputEvent: "numpad-click-input",
triggerAtInput: "update-selected-orderline",
});
@@ -89,7 +118,7 @@ class TicketScreen extends IndependentToOrderScreen {
this._state.ui.selectedOrderlineIds[clickedOrder.backendId] = firstLine.id;
}
}
NumberBuffer.reset();
numberBuffer.reset();
} else {
this._setOrder(clickedOrder);
}
@@ -111,7 +140,7 @@ class TicketScreen extends IndependentToOrderScreen {
["ProductScreen", "PaymentScreen"].includes(screen.name) &&
order.get_orderlines().length > 0
) {
const { confirmed } = await this.showPopup("ConfirmPopup", {
const { confirmed } = await this.showPopup(ConfirmPopup, {
title: this.env._t("Existing orderlines"),
body: _.str.sprintf(
this.env._t(
@@ -151,7 +180,7 @@ class TicketScreen extends IndependentToOrderScreen {
_onClickOrderline({ detail: orderline }) {
const order = this.getSelectedSyncedOrder();
this._state.ui.selectedOrderlineIds[order.backendId] = orderline.id;
NumberBuffer.reset();
numberBuffer.reset();
}
_onClickRefundOrderUid({ detail: orderUid }) {
// Open the refund order.
@@ -164,24 +193,24 @@ class TicketScreen extends IndependentToOrderScreen {
const buffer = detail.buffer;
const order = this.getSelectedSyncedOrder();
if (!order) {
return NumberBuffer.reset();
return numberBuffer.reset();
}
const selectedOrderlineId = this.getSelectedOrderlineId();
const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
if (!orderline) {
return NumberBuffer.reset();
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();
return numberBuffer.reset();
}
const refundableQty = toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty;
if (refundableQty <= 0) {
return NumberBuffer.reset();
return numberBuffer.reset();
}
if (buffer == null || buffer == "") {
@@ -189,8 +218,8 @@ class TicketScreen extends IndependentToOrderScreen {
} else {
const quantity = Math.abs(parse.float(buffer));
if (quantity > refundableQty) {
NumberBuffer.reset();
this.showPopup("ErrorPopup", {
numberBuffer.reset();
this.showPopup(ErrorPopup, {
title: this.env._t("Maximum Exceeded"),
body: _.str.sprintf(
this.env._t(
@@ -665,7 +694,7 @@ class TicketScreen extends IndependentToOrderScreen {
// 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(
this._state.syncedOrders.cache[order.id] = new Order(
{},
{ pos: this.env.pos, json: order }
);
@@ -687,17 +716,5 @@ class TicketScreen extends IndependentToOrderScreen {
//#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);
export default TicketScreen;
registry.category("pos_screens").add("TicketScreen", TicketScreen);
@@ -8,7 +8,7 @@ var Mutex = concurrency.Mutex;
// is set-up to act like a keyboard. Use connect() and disconnect() to activate
// 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({
export const BarcodeReader = core.Class.extend({
actions: ["product", "cashier", "client"],
init: function (attributes) {
@@ -163,5 +163,3 @@ var BarcodeReader = core.Class.extend({
this.remote_scanning = false;
},
});
export default BarcodeReader;
@@ -1,4 +1,9 @@
/** @odoo-module */
import { OfflineErrorPopup } from "./Popups/OfflineErrorPopup";
import { ConfirmPopup } from "./Popups/ConfirmPopup";
import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup";
import { ErrorPopup } from "./Popups/ErrorPopup";
const { onMounted, onPatched, onWillUnmount, useComponent } = owl;
/**
@@ -13,7 +18,7 @@ export function useErrorHandlers() {
component._handlePushOrderError = async function (error) {
// This error handler receives `error` equivalent to `error.message` of the rpc error.
if (error.message === "Backend Invoice") {
await this.showPopup("ConfirmPopup", {
await this.showPopup(ConfirmPopup, {
title: this.env._t("Please print the invoice from the backend"),
body:
this.env._t(
@@ -26,10 +31,10 @@ export function useErrorHandlers() {
const body = this.env._t(
"Check the internet connection then try to sync again by clicking on the red wifi button (upper right of the screen)."
);
await this.showPopup("OfflineErrorPopup", { title, body });
await this.showPopup(OfflineErrorPopup, { title, body });
} else if (error.code === 200) {
// OpenERP Server Errors
await this.showPopup("ErrorTracebackPopup", {
await this.showPopup(ErrorTracebackPopup, {
title: error.data.message || this.env._t("Server Error"),
body:
error.data.debug ||
@@ -37,7 +42,7 @@ export function useErrorHandlers() {
});
} else if (error.code === 700) {
// Sweden Fiscal module errors
await this.showPopup("ErrorPopup", {
await this.showPopup(ErrorPopup, {
title: this.env._t("Fiscal data module error"),
body:
error.data.error.status ||
@@ -53,13 +58,13 @@ export function useErrorHandlers() {
} else {
bodyMessage = "Fiscal data module is not on.";
}
await this.showPopup("ErrorPopup", {
await this.showPopup(ErrorPopup, {
title: this.env._t("Fiscal data module error"),
body: bodyMessage,
});
} else {
// ???
await this.showPopup("ErrorPopup", {
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"
+1 -3
View File
@@ -18,7 +18,7 @@ import utils from "web.utils";
*/
const CACHE = {};
var PosDB = core.Class.extend({
export const 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) {
@@ -583,5 +583,3 @@ var PosDB = core.Class.extend({
this.save("unpaid_orders_to_remove", to_remove);
},
});
export default PosDB;
+29 -31
View File
@@ -1,7 +1,7 @@
/** @odoo-module */
/* global waitForWebfonts */
import PosDB from "@point_of_sale/js/db";
import { PosDB } from "@point_of_sale/js/db";
import config from "web.config";
import core from "web.core";
import field_utils from "web.field_utils";
@@ -9,13 +9,13 @@ import time from "web.time";
import utils from "web.utils";
import { Gui } from "@point_of_sale/js/Gui";
import { batched, uuidv4 } from "@point_of_sale/js/utils";
import { ErrorPopup } from "./Popups/ErrorPopup";
var QWeb = core.qweb;
var _t = core._t;
var round_di = utils.round_decimals;
var round_pr = utils.round_precision;
import Registries from "@point_of_sale/js/Registries";
const { markRaw, reactive } = owl;
// Container of the product images fetched during rendering
@@ -60,7 +60,11 @@ class PosModel {
* don't want this default calculation of cid.
* @param {Object?} defaultObj its props copied to this instance.
*/
constructor(defaultObj) {
constructor() {
this.setup(...arguments);
}
// To be used by Model patches to patch constructor
setup(defaultObj) {
defaultObj = defaultObj || {};
if (!defaultObj.cid) {
defaultObj.cid = this._getCID(defaultObj);
@@ -84,8 +88,8 @@ class PosModel {
}
export class PosGlobalState extends PosModel {
constructor(obj) {
super(obj);
setup() {
super.setup(...arguments);
this.db = new PosDB(); // a local database used to search trough products and categories & store pending orders
this.debug = config.isDebug(); //debug mode
@@ -240,7 +244,7 @@ export class PosGlobalState extends PosModel {
productTemplateMap[product.product_tmpl_id[0]] = (
productTemplateMap[product.product_tmpl_id[0]] || []
).concat(product);
return Product.create(product);
return new Product(product);
});
for (const pricelist of this.pricelists) {
@@ -405,7 +409,7 @@ export class PosGlobalState extends PosModel {
if (json) {
options.json = json;
}
return this.makeOrderReactive(Order.create({}, options));
return this.makeOrderReactive(new Order({}, options));
}
makeOrderReactive(order) {
const batchedCallback = batched(() => {
@@ -1425,7 +1429,6 @@ export class PosGlobalState extends PosModel {
}
}
PosGlobalState.prototype.electronic_payment_interfaces = {};
Registries.Model.add(PosGlobalState);
/**
* Call this function to map your PaymentInterface implementation to
@@ -1497,7 +1500,7 @@ export class Product extends PosModel {
var pricelist_items = [];
if (pricelist) {
var pricelist_items = _.filter(
pricelist_items = _.filter(
self.applicablePricelistItems[pricelist.id],
function (item) {
return (
@@ -1578,7 +1581,6 @@ export class Product extends PosModel {
}
}
}
Registries.Model.add(Product);
var orderline_id = 1;
@@ -1586,8 +1588,8 @@ var orderline_id = 1;
// An orderline contains a product, its quantity, its price, discount. etc.
// An Order contains zero or more Orderlines.
export class Orderline extends PosModel {
constructor(obj, options) {
super(obj);
setup(_defaultObj, options) {
super.setup(...arguments);
this.pos = options.pos;
this.order = options.order;
this.price_manually_set = options.price_manually_set || false;
@@ -1637,7 +1639,7 @@ export class Orderline extends PosModel {
var pack_lot_lines = json.pack_lot_ids;
for (var i = 0; i < pack_lot_lines.length; i++) {
var packlotline = pack_lot_lines[i][2];
var pack_lot_line = Packlotline.create(
var pack_lot_line = new Packlotline(
{},
{ json: _.extend({...packlotline}, { order_line: this }) }
);
@@ -1649,7 +1651,7 @@ export class Orderline extends PosModel {
this.refunded_orderline_id = json.refunded_orderline_id;
}
clone() {
var orderline = Orderline.create(
var orderline = new Orderline(
{},
{
pos: this.pos,
@@ -1712,7 +1714,7 @@ export class Orderline extends PosModel {
// Create new pack lot lines.
let newPackLotLine;
for (const newLotLine of newPackLotLines) {
newPackLotLine = Packlotline.create({}, { order_line: this });
newPackLotLine = new Packlotline({}, { order_line: this });
newPackLotLine.lot_name = newLotLine.lot_name;
this.pack_lot_lines.add(newPackLotLine);
}
@@ -1779,7 +1781,7 @@ export class Orderline extends PosModel {
const maxQtyToRefund =
toRefundDetail.orderline.qty - toRefundDetail.orderline.refundedQty;
if (quant > 0) {
Gui.showPopup("ErrorPopup", {
Gui.showPopup(ErrorPopup, {
title: _t("Positive quantity not allowed"),
body: _t(
"Only a negative quantity is allowed for this refund line. Click on +/- to modify the quantity to be refunded."
@@ -1791,7 +1793,7 @@ export class Orderline extends PosModel {
} else if (-quant <= maxQtyToRefund) {
toRefundDetail.qty = -quant;
} else {
Gui.showPopup("ErrorPopup", {
Gui.showPopup(ErrorPopup, {
title: _t("Greater than allowed"),
body: _.str.sprintf(
_t(
@@ -2308,11 +2310,10 @@ export class Orderline extends PosModel {
return this.product.standard_price * this.quantity;
}
}
Registries.Model.add(Orderline);
export class Packlotline extends PosModel {
constructor(obj, options) {
super(obj);
setup(_defaultObj, options) {
super.setup(...arguments);
this.lot_name = null;
this.order_line = options.order_line;
if (options.json) {
@@ -2340,12 +2341,11 @@ export class Packlotline extends PosModel {
};
}
}
Registries.Model.add(Packlotline);
// Every Paymentline contains a cashregister and an amount of money.
export class Payment extends PosModel {
constructor(obj, options) {
super(obj);
setup(obj, options) {
super.setup(...arguments);
this.pos = options.pos;
this.order = options.order;
this.amount = 0;
@@ -2475,15 +2475,14 @@ export class Payment extends PosModel {
return Boolean(this.get_payment_status());
}
}
Registries.Model.add(Payment);
// An order more or less represents the content of a customer's shopping cart (the OrderLines)
// plus the associated payment information (the Paymentlines)
// there is always an active ('selected') order in the Pos, a new one is created
// automaticaly once an order is completed and sent to the server.
export class Order extends PosModel {
constructor(obj, options) {
super(obj);
setup(_defaultObj, options) {
super.setup(...arguments);
var self = this;
options = options || {};
@@ -2606,7 +2605,7 @@ export class Order extends PosModel {
var orderline = orderlines[i][2];
if (this.pos.db.get_product_by_id(orderline.product_id)) {
this.add_orderline(
Orderline.create({}, { pos: this.pos, order: this, json: orderline })
new Orderline({}, { pos: this.pos, order: this, json: orderline })
);
}
}
@@ -2614,7 +2613,7 @@ export class Order extends PosModel {
var paymentlines = json.statement_ids;
for (i = 0; i < paymentlines.length; i++) {
var paymentline = paymentlines[i][2];
var newpaymentline = Payment.create(
var newpaymentline = new Payment(
{},
{ pos: this.pos, order: this, json: paymentline }
);
@@ -2990,7 +2989,7 @@ export class Order extends PosModel {
}
this.assert_editable();
options = options || {};
var line = Orderline.create({}, { pos: this.pos, order: this, product: product });
var line = new Orderline({}, { pos: this.pos, order: this, product: product });
this.fix_tax_included_price(line);
this.set_orderline_options(line, options);
@@ -3097,7 +3096,7 @@ export class Order extends PosModel {
if (this.electronic_payment_in_progress()) {
return false;
} else {
var newPaymentline = Payment.create(
var newPaymentline = new Payment(
{},
{ order: this, payment_method: payment_method, pos: this.pos }
);
@@ -3617,4 +3616,3 @@ export class Order extends PosModel {
}
}
}
Registries.Model.add(Order);
@@ -5,7 +5,7 @@ import core from "web.core";
/**
* Implement this interface to support a new payment method in the POS:
*
import var PaymentInterface from "@point_of_sale/js/payment";
import const PaymentInterface from "@point_of_sale/js/payment";
* var MyPayment = PaymentInterface.extend({
* ...
* })
@@ -22,7 +22,7 @@ import core from "web.core";
* by overriding the loader_params of the models in the back end
* in the `pos.session` model
*/
var PaymentInterface = core.Class.extend({
export const PaymentInterface = core.Class.extend({
init: function (pos, payment_method) {
this.pos = pos;
this.payment_method = payment_method;
@@ -89,5 +89,3 @@ var PaymentInterface = core.Class.extend({
*/
close: function () {},
});
export default PaymentInterface;
@@ -6,16 +6,14 @@
import env from "web.env";
import concurrency from "web.concurrency";
import { JobQueue, ProxyDevice } from "@point_of_sale/js/devices";
import BarcodeReader from "@point_of_sale/js/barcode_reader";
import { BarcodeReader } from "@point_of_sale/js/barcode_reader";
// Create new env object base on web.env.
// Mutating this new object won't affect the original object.
const pos_env = Object.create(env);
export const pos_env = Object.create(env);
pos_env.proxy_queue = new JobQueue(); // used to prevent parallels communications to the proxy
pos_env.proxy = new ProxyDevice({ env: pos_env }); // used to communicate to the hardware devices via a local proxy
pos_env.barcode_reader = new BarcodeReader({ env: pos_env, proxy: pos_env.proxy });
pos_env.posbus = new owl.EventBus();
pos_env.posMutex = new concurrency.Mutex();
export default pos_env;
@@ -4,6 +4,7 @@
import Session from "web.Session";
import core from "web.core";
import { Gui } from "@point_of_sale/js/Gui";
import { ErrorPopup } from "./Popups/ErrorPopup";
var _t = core._t;
// IMPROVEMENT: This is too much. We can get away from this class.
@@ -114,7 +115,7 @@ export const PrinterMixin = {
_onIoTActionResult: function (data) {
if (this.pos && (data === false || data.result === false)) {
Gui.showPopup("ErrorPopup", {
Gui.showPopup(ErrorPopup, {
title: _t("Connection to the printer failed"),
body: _t("Please check if the printer is still connected."),
});
@@ -123,7 +124,7 @@ export const PrinterMixin = {
_onIoTActionFail: function () {
if (this.pos) {
Gui.showPopup("ErrorPopup", {
Gui.showPopup(ErrorPopup, {
title: _t("Connection to IoT Box failed"),
body: _t("Please check if the IoT Box is still connected."),
});
@@ -3,9 +3,9 @@
<t t-name="point_of_sale.PosPopupController" owl="1">
<div class="popups" t-att-hidden="!isShown()">
<t t-foreach="popups" t-as="popup" t-key="popup.key">
<t t-foreach="popups" t-as="popup" t-key="popup.props.id">
<div role="dialog" class="modal-dialog" t-att-class="{ oe_hidden: !shouldShow(popup) }">
<t t-component="popup.component" t-props="popup.props" t-key="popup.key" />
<t t-component="popup.component" t-props="popup.props" />
</div>
</t>
</div>

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