+
+
+
+
+
+
+
+
+
+
diff --git a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.js b/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.js
deleted file mode 100644
index a52ee339854..00000000000
--- a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/** @odoo-module */
-
-import { OrderlineDetails } from "@point_of_sale/app/screens/ticket_screen/order_details/orderline_details";
-import { Component } from "@odoo/owl";
-
-/**
- * @props {models.Order} order
- */
-export class OrderDetails extends Component {
- static components = { OrderlineDetails };
- static template = "point_of_sale.OrderDetails";
-
- get order() {
- return this.props.order;
- }
- get orderlines() {
- return this.order ? this.order.orderlines : [];
- }
- get total() {
- return this.env.utils.formatCurrency(this.order ? this.order.get_total_with_tax() : 0);
- }
- get tax() {
- return this.env.utils.formatCurrency(this.order ? this.order.get_total_tax() : 0);
- }
-}
diff --git a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.xml b/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.xml
deleted file mode 100644
index 54978b97b6e..00000000000
--- a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.js b/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.js
deleted file mode 100644
index 5c8a141ed4c..00000000000
--- a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/** @odoo-module **/
-
-import { _t } from "@web/core/l10n/translation";
-import { Component } from "@odoo/owl";
-import { formatFloat, roundPrecision as round_pr } from "@web/core/utils/numbers";
-import { usePos } from "@point_of_sale/app/store/pos_hook";
-
-/**
- * @props {pos.order.line} line
- */
-export class OrderlineDetails extends Component {
- static template = "point_of_sale.OrderlineDetails";
-
- setup() {
- this.pos = usePos();
- }
- get line() {
- const line = this.props.line;
- const formatQty = (line) => {
- const quantity = line.get_quantity();
- const unit = line.get_unit();
- const decimals = this.pos.dp["Product Unit of Measure"];
- const rounding = Math.max(unit.rounding, Math.pow(10, -decimals));
- const roundedQuantity = round_pr(quantity, rounding);
- return formatFloat(roundedQuantity, { digits: [69, decimals] });
- };
- return {
- productName: line.get_full_product_name(),
- totalPrice: line.get_price_with_tax(),
- quantity: formatQty(line),
- unit: line.get_unit().name,
- unitPrice: line.get_unit_price(),
- };
- }
- get productName() {
- return this.line.productName;
- }
- get totalPrice() {
- return this.env.utils.formatCurrency(this.line.totalPrice);
- }
- get quantity() {
- return this.line.quantity;
- }
- get unitPrice() {
- return this.env.utils.formatCurrency(this.line.unitPrice);
- }
- get unit() {
- return this.line.unit;
- }
- get pricePerUnit() {
- return ` ${this.unit} at ${this.unitPrice} / ${this.unit}`;
- }
- get customerNote() {
- return this.props.line.get_customer_note();
- }
- getToRefundDetail() {
- return this.pos.toRefundLines[this.props.line.id];
- }
- hasRefundedQty() {
- return !this.pos.isProductQtyZero(this.props.line.refunded_qty);
- }
- getFormattedRefundedQty() {
- return this.env.utils.formatProductQty(this.props.line.refunded_qty);
- }
- hasToRefundQty() {
- const toRefundDetail = this.getToRefundDetail();
- return !this.pos.isProductQtyZero(toRefundDetail && toRefundDetail.qty);
- }
- getFormattedToRefundQty() {
- const toRefundDetail = this.getToRefundDetail();
- return this.env.utils.formatProductQty(toRefundDetail && toRefundDetail.qty);
- }
- getRefundingMessage() {
- return _t("Refunding %s in ", this.getFormattedToRefundQty());
- }
- getToRefundMessage() {
- return _t("To Refund: %s", this.getFormattedToRefundQty());
- }
-}
diff --git a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.xml b/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.xml
deleted file mode 100644
index 77315640cb2..00000000000
--- a/addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Refunded
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.js b/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.js
index e6495f4728c..937e279bc8b 100644
--- a/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.js
+++ b/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.js
@@ -12,7 +12,9 @@ import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_pop
import { ActionpadWidget } from "@point_of_sale/app/screens/product_screen/action_pad/action_pad";
import { InvoiceButton } from "@point_of_sale/app/screens/ticket_screen/invoice_button/invoice_button";
-import { OrderDetails } from "@point_of_sale/app/screens/ticket_screen/order_details/order_details";
+import { Orderline } from "@point_of_sale/app/generic_components/orderline/orderline";
+import { OrderWidget } from "@point_of_sale/app/generic_components/order_widget/order_widget";
+import { CenteredIcon } from "@point_of_sale/app/generic_components/centered_icon/centered_icon";
import { ReprintReceiptButton } from "@point_of_sale/app/screens/ticket_screen/reprint_receipt_button/reprint_receipt_button";
import { SearchBar } from "@point_of_sale/app/screens/ticket_screen/search_bar/search_bar";
import { usePos } from "@point_of_sale/app/store/pos_hook";
@@ -27,7 +29,9 @@ export class TicketScreen extends Component {
static components = {
ActionpadWidget,
InvoiceButton,
- OrderDetails,
+ Orderline,
+ OrderWidget,
+ CenteredIcon,
ReprintReceiptButton,
SearchBar,
Numpad,
diff --git a/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.xml b/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.xml
index c729c1ab7c5..e8916302632 100644
--- a/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.xml
+++ b/addons/point_of_sale/static/src/app/screens/ticket_screen/ticket_screen.xml
@@ -105,12 +105,7 @@
-
-
-
-
No orders found
-
-
+
@@ -125,43 +120,64 @@
-
-
+
+
+ Select the product(s) to refund and set the quantity
+
+
+
+
+
+
+
+ Refunded
+
+
+
+
+
+ Refunding in
+
+
+
+ To Refund:
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Load Order
+
-
-
-
-
- Load Order
-
-
-
+
diff --git a/addons/point_of_sale/static/src/app/store/models.js b/addons/point_of_sale/static/src/app/store/models.js
index 2a74127fc5f..37cb60094bd 100644
--- a/addons/point_of_sale/static/src/app/store/models.js
+++ b/addons/point_of_sale/static/src/app/store/models.js
@@ -19,7 +19,6 @@ import {
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { ProductConfiguratorPopup } from "@point_of_sale/app/store/product_configurator_popup/product_configurator_popup";
import { ComboConfiguratorPopup } from "./combo_configurator_popup/combo_configurator_popup";
-import { EditListPopup } from "@point_of_sale/app/store/select_lot_popup/select_lot_popup";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { _t } from "@web/core/l10n/translation";
import { renderToElement } from "@web/core/utils/render";
@@ -113,6 +112,12 @@ export class Product extends PosModel {
""
);
}
+ isTracked() {
+ return (
+ ["serial", "lot"].includes(this.tracking) &&
+ (this.pos.picking_type.use_create_lots || this.pos.picking_type.use_existing_lots)
+ );
+ }
get_unit() {
var unit_id = this.uom_id;
if (!unit_id) {
@@ -179,24 +184,15 @@ export class Product extends PosModel {
comboLines = payload;
}
// Gather lot information if required.
- if (
- ["serial", "lot"].includes(this.tracking) &&
- (this.pos.picking_type.use_create_lots || this.pos.picking_type.use_existing_lots)
- ) {
- const isAllowOnlyOneLot = this.isAllowOnlyOneLot();
- if (isAllowOnlyOneLot) {
- packLotLinesToEdit = [];
- } else {
- const orderline = this.pos.selectedOrder
- .get_orderlines()
- .filter((line) => !line.get_discount())
- .find((line) => line.product.id === this.id);
- if (orderline) {
- packLotLinesToEdit = orderline.getPackLotLinesToEdit();
- } else {
- packLotLinesToEdit = [];
- }
- }
+ if (this.isTracked()) {
+ packLotLinesToEdit =
+ (!this.isAllowOnlyOneLot() &&
+ this.pos.selectedOrder
+ .get_orderlines()
+ .filter((line) => !line.get_discount())
+ .find((line) => line.product.id === this.id)
+ ?.getPackLotLinesToEdit()) ||
+ [];
// if the lot information exists in the barcode, we don't need to ask it from the user.
if (code && code.type === "lot") {
// consider the old and new packlot lines
@@ -206,31 +202,14 @@ export class Product extends PosModel {
const newPackLotLines = [{ lot_name: code.code }];
draftPackLotLines = { modifiedPackLotLines, newPackLotLines };
} else {
- const { confirmed, payload } = await this.pos.env.services.popup.add(
- EditListPopup,
- {
- title: _t("Lot/Serial Number(s) Required"),
- name: this.display_name,
- isSingleItem: isAllowOnlyOneLot,
- array: packLotLinesToEdit,
- }
+ draftPackLotLines = await this.pos.getEditedPackLotLines(
+ this.isAllowOnlyOneLot(),
+ packLotLinesToEdit,
+ this.display_name
);
- if (confirmed) {
- // Segregate the old and new packlot lines
- const modifiedPackLotLines = Object.fromEntries(
- payload.newArray
- .filter((item) => item.id)
- .map((item) => [item.id, item.text])
- );
- const newPackLotLines = payload.newArray
- .filter((item) => !item.id)
- .map((item) => ({ lot_name: item.text }));
-
- draftPackLotLines = { modifiedPackLotLines, newPackLotLines };
- } else {
- // We don't proceed on adding product.
- return;
- }
+ }
+ if (!draftPackLotLines) {
+ return;
}
}
@@ -470,6 +449,9 @@ export class Orderline extends PosModel {
orderline.customerNote = this.customerNote;
return orderline;
}
+ getDisplayClasses() {
+ return {};
+ }
getPackLotLinesToEdit(isAllowOnlyOneLot) {
const currentPackLotLines = this.pack_lot_lines;
let nExtraLines = Math.abs(this.quantity) - currentPackLotLines.length;
@@ -487,6 +469,23 @@ export class Orderline extends PosModel {
);
return isAllowOnlyOneLot ? [tempLines[0]] : tempLines;
}
+ // What if a number different from 1 (or -1) is specified
+ // to an orderline that has product tracked by lot? Lot tracking (based
+ // on the current implementation) requires that 1 item per orderline is
+ // allowed.
+ async editPackLotLines() {
+ const isAllowOnlyOneLot = this.product.isAllowOnlyOneLot();
+ const editedPackLotLines = await this.pos.getEditedPackLotLines(
+ isAllowOnlyOneLot,
+ this.getPackLotLinesToEdit(isAllowOnlyOneLot),
+ this.product.display_name
+ );
+ if (!editedPackLotLines) {
+ return;
+ }
+ this.setPackLotLines(editedPackLotLines);
+ this.order.select_orderline(this);
+ }
/**
* @param { modifiedPackLotLines, newPackLotLines }
* @param {Object} modifiedPackLotLines key-value pair of String (the cid) & String (the new lot_name)
@@ -534,11 +533,6 @@ export class Orderline extends PosModel {
setNote(note) {
this.note = note;
}
- toggleSkipChange() {
- if (this.hasChange || this.skipChange) {
- this.skipChange = !this.skipChange;
- }
- }
setHasChange(isChange) {
this.hasChange = isChange;
}
@@ -879,6 +873,18 @@ export class Orderline extends PosModel {
return this.get_all_prices(1).priceWithoutTax;
}
}
+ /**
+ * This is the price that will appear as striked through.
+ * @returns {number | false}
+ */
+ get_old_unit_display_price() {
+ return (
+ this.display_discount_policy() === "without_discount" &&
+ this.env.utils.roundCurrency(this.get_unit_display_price()) <
+ this.env.utils.roundCurrency(this.get_taxed_lst_unit_price()) &&
+ this.get_taxed_lst_unit_price()
+ );
+ }
getUnitDisplayPriceBeforeDiscount() {
if (this.pos.config.iface_tax_included === "total") {
return this.get_all_prices(1).priceWithTaxBeforeDiscount;
@@ -1120,6 +1126,20 @@ export class Orderline extends PosModel {
isPartOfCombo() {
return Boolean(this.comboParent || this.comboLines?.length);
}
+ getDisplayData() {
+ return {
+ productName: this.get_full_product_name(),
+ price: this.env.utils.formatCurrency(this.get_display_price()),
+ qty: this.get_quantity_str(),
+ unit: this.get_unit().name,
+ unitPrice: this.env.utils.formatCurrency(this.get_unit_display_price()),
+ oldUnitPrice: this.env.utils.formatCurrency(this.get_old_unit_display_price()),
+ discount: this.get_discount_str(),
+ customerNote: this.get_customer_note(),
+ internalNote: this.getNote(),
+ comboParent: this.comboParent?.get_full_product_name(),
+ };
+ }
}
export class Packlotline extends PosModel {
diff --git a/addons/point_of_sale/static/src/app/store/pos_store.js b/addons/point_of_sale/static/src/app/store/pos_store.js
index d2727031995..fbd5294eb13 100644
--- a/addons/point_of_sale/static/src/app/store/pos_store.js
+++ b/addons/point_of_sale/static/src/app/store/pos_store.js
@@ -21,6 +21,7 @@ import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product
import { renderToString } from "@web/core/utils/render";
import { batched } from "@web/core/utils/timing";
import { TicketScreen } from "@point_of_sale/app/screens/ticket_screen/ticket_screen";
+import { EditListPopup } from "./select_lot_popup/select_lot_popup";
/* Returns an array containing all elements of the given
* array corresponding to the rule function {agg} and without duplicates
@@ -1823,6 +1824,28 @@ export class PosStore extends Reactive {
await this.addProductFromUi(product, options);
this.numberBuffer.reset();
}
+
+ async getEditedPackLotLines(isAllowOnlyOneLot, packLotLinesToEdit, productName) {
+ const { confirmed, payload } = await this.env.services.popup.add(EditListPopup, {
+ title: _t("Lot/Serial Number(s) Required"),
+ name: productName,
+ isSingleItem: isAllowOnlyOneLot,
+ array: packLotLinesToEdit,
+ });
+ if (!confirmed) {
+ return;
+ }
+ // Segregate the old and new packlot lines
+ const modifiedPackLotLines = Object.fromEntries(
+ payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text])
+ );
+ const newPackLotLines = payload.newArray
+ .filter((item) => !item.id)
+ .map((item) => ({ lot_name: item.text }));
+
+ return { modifiedPackLotLines, newPackLotLines };
+ }
+
// FIXME POSREF get rid of temp screens entirely?
showTempScreen(name, props = {}) {
return new Promise((resolve) => {
diff --git a/addons/point_of_sale/static/tests/tours/PosComboTour.js b/addons/point_of_sale/static/tests/tours/PosComboTour.js
index 0772690d8aa..1fc028d7141 100644
--- a/addons/point_of_sale/static/tests/tours/PosComboTour.js
+++ b/addons/point_of_sale/static/tests/tours/PosComboTour.js
@@ -38,7 +38,7 @@ registry.category("web_tour.tours").add("PosComboPriceTaxIncludedTour", {
isCheck: true,
},
combo.confirm(),
- ...ProductScreen.do.clickOrderline("Office Combo"),
+ ...ProductScreen.check.selectedOrderlineHas("Office Combo"),
...ProductScreen.do.clickOrderline("Combo Product 3"),
...ProductScreen.check.selectedOrderlineHas(
"Combo Product 3",
diff --git a/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js b/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js
index 57526beca19..44868d30c03 100644
--- a/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js
+++ b/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js
@@ -6,6 +6,7 @@ import { ReceiptScreen } from "@point_of_sale/../tests/tours/helpers/ReceiptScre
import { TextAreaPopup } from "@point_of_sale/../tests/tours/helpers/TextAreaPopupTourMethods";
import { getSteps, startSteps } from "@point_of_sale/../tests/tours/helpers/utils";
import { registry } from "@web/core/registry";
+import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
registry.category("web_tour.tours").add("ProductScreenTour", {
test: true,
@@ -78,11 +79,9 @@ registry.category("web_tour.tours").add("ProductScreenTour", {
ProductScreen.do.clickDisplayedProduct("Whiteboard Pen");
ProductScreen.do.clickDisplayedProduct("Wall Shelf Unit");
ProductScreen.do.clickOrderline("Whiteboard Pen", "1.0");
- ProductScreen.check.selectedOrderlineHas("Whiteboard Pen", "1.0");
ProductScreen.do.pressNumpad("2");
ProductScreen.check.selectedOrderlineHas("Whiteboard Pen", "2.0");
ProductScreen.do.clickOrderline("Wall Shelf Unit", "1.0");
- ProductScreen.check.selectedOrderlineHas("Wall Shelf Unit", "1.0");
ProductScreen.do.pressNumpad("2");
ProductScreen.check.selectedOrderlineHas("Wall Shelf Unit", "2.0");
ProductScreen.do.pressNumpad("⌫");
@@ -101,25 +100,21 @@ registry.category("web_tour.tours").add("ProductScreenTour", {
ProductScreen.do.clickDisplayedProduct("Magnetic Board");
ProductScreen.do.clickDisplayedProduct("Monitor Stand");
ProductScreen.do.clickOrderline("Whiteboard Pen", "1.0");
- ProductScreen.check.selectedOrderlineHas("Whiteboard Pen", "1.0");
ProductScreen.do.pressNumpad("⌫");
ProductScreen.check.selectedOrderlineHas("Whiteboard Pen", "0.0");
ProductScreen.do.pressNumpad("⌫");
ProductScreen.check.selectedOrderlineHas("Monitor Stand", "1.0");
ProductScreen.do.clickOrderline("Wall Shelf Unit", "1.0");
- ProductScreen.check.selectedOrderlineHas("Wall Shelf Unit", "1.0");
ProductScreen.do.pressNumpad("⌫");
ProductScreen.check.selectedOrderlineHas("Wall Shelf Unit", "0.0");
ProductScreen.do.pressNumpad("⌫");
ProductScreen.check.selectedOrderlineHas("Monitor Stand", "1.0");
ProductScreen.do.clickOrderline("Small Shelf", "1.0");
- ProductScreen.check.selectedOrderlineHas("Small Shelf", "1.0");
ProductScreen.do.pressNumpad("⌫");
ProductScreen.check.selectedOrderlineHas("Small Shelf", "0.0");
ProductScreen.do.pressNumpad("⌫");
ProductScreen.check.selectedOrderlineHas("Monitor Stand", "1.0");
ProductScreen.do.clickOrderline("Magnetic Board", "1.0");
- ProductScreen.check.selectedOrderlineHas("Magnetic Board", "1.0");
ProductScreen.do.pressNumpad("⌫");
ProductScreen.check.selectedOrderlineHas("Magnetic Board", "0.0");
ProductScreen.do.pressNumpad("⌫");
@@ -136,7 +131,13 @@ registry.category("web_tour.tours").add("ProductScreenTour", {
TextAreaPopup.check.isShown();
TextAreaPopup.do.inputText("Test customer note");
TextAreaPopup.do.clickConfirm();
- ProductScreen.check.orderlineHasCustomerNote("Desk Organizer", "1", "Test customer note");
+ Order.hasLine({
+ productName: "Desk Organizer",
+ quantity: "1.0",
+ customerNote: "Test customer note",
+ withClass: ".selected",
+ });
+ ProductScreen.check.isShown();
return getSteps();
},
});
diff --git a/addons/point_of_sale/static/tests/tours/TicketScreen.tour.js b/addons/point_of_sale/static/tests/tours/TicketScreen.tour.js
index 37bc5f24f20..8eda3566583 100644
--- a/addons/point_of_sale/static/tests/tours/TicketScreen.tour.js
+++ b/addons/point_of_sale/static/tests/tours/TicketScreen.tour.js
@@ -5,6 +5,7 @@ import { ReceiptScreen } from "@point_of_sale/../tests/tours/helpers/ReceiptScre
import { PaymentScreen } from "@point_of_sale/../tests/tours/helpers/PaymentScreenTourMethods";
import { PartnerListScreen } from "@point_of_sale/../tests/tours/helpers/PartnerListScreenTourMethods";
import { TicketScreen } from "@point_of_sale/../tests/tours/helpers/TicketScreenTourMethods";
+import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
import { ErrorPopup } from "@point_of_sale/../tests/tours/helpers/ErrorPopupTourMethods";
import { Chrome } from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods";
import { getSteps, startSteps } from "@point_of_sale/../tests/tours/helpers/utils";
@@ -125,7 +126,7 @@ registry.category("web_tour.tours").add("TicketScreenTour", {
TicketScreen.check.filterIs("Paid");
TicketScreen.do.selectOrder("-0005");
TicketScreen.check.partnerIs("Colleen Diaz");
- TicketScreen.do.clickOrderline("Desk Pad");
+ Order.hasLine({ productName: "Desk Pad", withClass: ".selected" });
ProductScreen.do.pressNumpad("3");
// Error should show because 2 is more than the number
// that can be refunded.
@@ -136,7 +137,7 @@ registry.category("web_tour.tours").add("TicketScreenTour", {
ProductScreen.check.orderIsEmpty();
ProductScreen.do.clickRefund();
TicketScreen.do.selectOrder("-0005");
- TicketScreen.do.clickOrderline("Desk Pad");
+ Order.hasLine({ productName: "Desk Pad", withClass: ".selected" });
ProductScreen.do.pressNumpad("1");
TicketScreen.check.toRefundTextContains("To Refund: 1.00");
TicketScreen.do.confirmRefund();
diff --git a/addons/point_of_sale/static/tests/tours/acceptance_tour.js b/addons/point_of_sale/static/tests/tours/acceptance_tour.js
index e7cee296c99..bcf9f6c6811 100644
--- a/addons/point_of_sale/static/tests/tours/acceptance_tour.js
+++ b/addons/point_of_sale/static/tests/tours/acceptance_tour.js
@@ -3,6 +3,7 @@
import { ProductScreen } from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods";
import { Numpad } from "@point_of_sale/../tests/tours/helpers/NumpadTourMethods";
import { registry } from "@web/core/registry";
+import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
function add_product_to_order(product_name) {
return [
@@ -15,11 +16,7 @@ function add_product_to_order(product_name) {
trigger: ".btn-switchpane:contains('Review')",
mobile: true,
},
- {
- content: "the " + product_name + " have been added to the order",
- trigger: '.order .product-name:contains("' + product_name + '")',
- run: function () {},
- },
+ ...Order.hasLine({ productName: product_name }),
{
content: "go back to the products",
trigger: ".floor-button",
@@ -130,20 +127,7 @@ function selected_orderline_has({ product, price = null, quantity = null }) {
mobile: true,
},
];
- if (price !== null) {
- result.push({
- content: `Selected line has product '${product}' and price '${price}'`,
- trigger: `.order-container .orderlines .orderline.selected .product-name:contains("${product}") ~ span.price:contains("${price}")`,
- run: function () {},
- });
- }
- if (quantity !== null) {
- result.push({
- content: `Selected line has product '${product}' and quantity '${quantity}'`,
- trigger: `.order-container .orderlines .orderline.selected .product-name:contains('${product}') ~ .info-list .info em:contains('${quantity}')`,
- run: function () {},
- });
- }
+ result.push(...Order.hasLine({ productName: product, quantity, price, withClass: ".selected" }));
result.push({
content: "go back to the products",
trigger: ".floor-button",
@@ -159,11 +143,7 @@ function verify_order_total(total_str) {
trigger: ".btn-switchpane:contains('Review')",
mobile: true,
},
- {
- content: "order total contains " + total_str,
- trigger: '.order .total .value:contains("' + total_str + '")',
- run: function () {}, // it's a check
- },
+ Order.hasTotal(total_str),
{
content: "go back to the products",
trigger: ".floor-button",
diff --git a/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js b/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js
index 9f439ea4ba1..eb7b48dc346 100644
--- a/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js
+++ b/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js
@@ -3,6 +3,35 @@
import { createTourMethods } from "@point_of_sale/../tests/tours/helpers/utils";
import { TextAreaPopup } from "@point_of_sale/../tests/tours/helpers/TextAreaPopupTourMethods";
import { Numpad } from "@point_of_sale/../tests/tours/helpers/NumpadTourMethods";
+import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
+
+export function adaptForMobile(steps) {
+ return [
+ {
+ content: "click review button",
+ trigger: ".btn-switchpane:contains('Review')",
+ mobile: true,
+ },
+ ...[steps].flat(),
+ {
+ content: "go back to the products",
+ trigger: ".pos-rightheader .floor-button",
+ mobile: true,
+ },
+ ];
+}
+
+export function clickLine(productName, quantity = "1.0") {
+ return adaptForMobile([
+ ...Order.hasLine({
+ withoutClass: ".selected",
+ run: "click",
+ productName,
+ quantity,
+ }),
+ ...Order.hasLine({ withClass: ".selected", productName, quantity }),
+ ]);
+}
class Do {
clickDisplayedProduct(name) {
return [
@@ -13,27 +42,9 @@ class Do {
];
}
- clickOrderline(name, quantity = "1.0") {
+ clickOrderline(productName, quantity = "1.0") {
return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- content: `selecting orderline with product '${name}' and quantity '${quantity}'`,
- trigger: `.order .orderline:not(:has(.selected)) .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
- },
- {
- content: `orderline with product '${name}' and quantity '${quantity}' has been selected`,
- trigger: `.order .orderline.selected .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
- run: () => {},
- },
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
+ ...clickLine(productName, quantity),
{
content: "Check the product page",
trigger: ".product-list-container .product-list",
@@ -73,19 +84,7 @@ class Do {
* @param {...String} keys space-separated numpad keys
*/
pressNumpad(...keys) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- ...keys.map(Numpad.click),
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile(keys.map(Numpad.click));
}
clickPayButton(shouldCheck = true) {
@@ -183,12 +182,7 @@ class Do {
return [{ trigger: '.opening-cash-control .button:contains("Open session")' }];
}
selectPriceList(name) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
+ return adaptForMobile([
{
content: "click more button",
trigger: ".mobile-more-button",
@@ -199,12 +193,7 @@ class Do {
content: `select price list '${name}'`,
trigger: `.selection-item:contains("${name}")`,
},
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ ]);
}
enterOpeningAmount(amount) {
return [
@@ -309,74 +298,25 @@ class Check {
];
}
selectedOrderlineHas(name, quantity, price, comboParent) {
- const res = [
+ return [
+ ...adaptForMobile(
+ Order.hasLine({
+ withClass: ".selected",
+ productName: name,
+ quantity,
+ price,
+ comboParent,
+ })
+ ),
{
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- // check first if the order widget is there and has orderlines
- content: "order widget has orderlines",
- trigger: ".order .orderlines",
- run: () => {},
- },
- {
- content: `'${name}' is selected`,
- trigger: `.order .orderline.selected .product-name:contains("${name}")`,
- run: function () {}, // it's a check
+ content: "Check the product page",
+ trigger: ".product-list-container .product-list",
+ isCheck: true,
},
];
- if (quantity) {
- res.push({
- content: `selected line has ${quantity} quantity`,
- trigger: `.order .orderline.selected .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
- run: function () {}, // it's a check
- });
- }
- if (price) {
- res.push({
- content: `selected line has total price of ${price}`,
- trigger: `.order .orderline.selected .product-name:contains("${name}") ~ .price:contains("${price}")`,
- run: function () {}, // it's a check
- });
- }
- if (comboParent) {
- res.push({
- content: `selected line has ${comboParent} as combo parent`,
- trigger: `.order .orderline.selected .info-list .combo-parent-name:contains("${comboParent}")`,
- isCheck: true,
- });
- }
- res.push({
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- }, {
- content: "Check the product page",
- trigger: ".product-list-container .product-list",
- isCheck: true,
- });
- return res;
}
orderIsEmpty() {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- content: `order is empty`,
- trigger: `.order .order-empty`,
- run: () => {},
- },
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile(Order.doesNotHaveLine());
}
productIsDisplayed(name) {
@@ -390,75 +330,19 @@ class Check {
}
totalAmountIs(amount) {
return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- content: `order total amount is '${amount}'`,
- trigger: `.order-container .order .summary .value:contains("${amount}")`,
- run: () => {},
- },
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
+ ...adaptForMobile(Order.hasTotal(amount)),
{
content: "Check the product page",
trigger: ".product-list-container .product-list",
isCheck: true,
- }
+ },
];
}
totalTaxIs(amount) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- content: `order total tax is '${amount}'`,
- trigger: `.summary .subentry .value:contains("${amount}")`,
- isCheck: true,
- },
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile(Order.hasTax(amount));
}
modeIsActive(mode) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- Numpad.isActive(mode),
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
- }
- orderlineHasCustomerNote(name, quantity, note) {
- return [
- {
- content: `line has ${quantity} quantity`,
- trigger: `.order .orderline .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
- run: function () {}, // it's a check
- },
- {
- content: `line has '${note}' as customer note`,
- trigger: `.order .orderline .info-list .orderline-note:contains("${note}")`,
- run: function () {}, // it's a check
- },
- ];
+ return adaptForMobile(Numpad.isActive(mode));
}
checkSecondCashClosingDetailsLineAmount(amount, sign) {
return [
@@ -483,22 +367,10 @@ class Check {
];
}
noDiscountApplied(originalPrice) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- content: "no discount is applied",
- trigger: `.info:not(:contains(${originalPrice}))`,
- },
- {
- content: "go back to the products",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile({
+ content: "no discount is applied",
+ trigger: `.orderline .info-list:not(:contains(${originalPrice}))`,
+ });
}
discountOriginalPriceIs(original_price) {
return [
@@ -576,11 +448,7 @@ class Execute {
if (quantity.toString() !== "1") {
res.push(...numpadWrite(quantity));
}
- if (expectedTotal) {
- res.push(...this._check.selectedOrderlineHas(productName, quantity, expectedTotal));
- } else {
- res.push(...this._check.selectedOrderlineHas(productName, quantity));
- }
+ res.push(...this._check.selectedOrderlineHas(productName, quantity, expectedTotal));
return res;
}
addMultiOrderlines(...list) {
diff --git a/addons/point_of_sale/static/tests/tours/helpers/TicketScreenTourMethods.js b/addons/point_of_sale/static/tests/tours/helpers/TicketScreenTourMethods.js
index d813b73aaab..d1900666e66 100644
--- a/addons/point_of_sale/static/tests/tours/helpers/TicketScreenTourMethods.js
+++ b/addons/point_of_sale/static/tests/tours/helpers/TicketScreenTourMethods.js
@@ -1,6 +1,11 @@
/** @odoo-module */
import { createTourMethods } from "@point_of_sale/../tests/tours/helpers/utils";
+import {
+ adaptForMobile,
+ clickLine,
+} from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods";
+import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
class Do {
clickNewTicket() {
@@ -130,25 +135,7 @@ class Do {
];
}
clickOrderline(name) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- trigger: `.ticket-screen .orderline:not(:has(.selected)) .product-name:contains("${name}")`,
- },
- {
- trigger: `.ticket-screen .orderline.selected .product-name:contains("${name}")`,
- run: () => {},
- },
- {
- content: "go back",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return clickLine(name);
}
confirmRefund() {
return [
@@ -179,6 +166,9 @@ class Check {
},
];
}
+ hasLine(options) {
+ return adaptForMobile(Order.hasLine(options));
+ }
/**
* Check if the nth row contains the given string.
* Note that 1st row is the header-row.
@@ -210,22 +200,7 @@ class Check {
];
}
orderWidgetIsNotEmpty() {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- trigger: ".ticket-screen:not(:has(.order-empty))",
- run: () => {},
- },
- {
- content: "go back",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile(Order.hasLine());
}
filterIs(name) {
return [
@@ -244,58 +219,22 @@ class Check {
];
}
partnerIs(name) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- trigger: `.ticket-screen .set-partner:contains("${name}")`,
- run: () => {},
- },
- {
- content: "Go back to main TicketScreen when in mobile",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile({
+ trigger: `.ticket-screen .set-partner:contains("${name}")`,
+ isCheck: true,
+ });
}
toRefundTextContains(text) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- trigger: `.ticket-screen .to-refund-highlight:contains("${text}")`,
- run: () => {},
- },
- {
- content: "Go back to main TicketScreen when in mobile",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile({
+ trigger: `.ticket-screen .to-refund-highlight:contains("${text}")`,
+ run: () => {},
+ });
}
refundedNoteContains(text) {
- return [
- {
- content: "click review button",
- trigger: ".btn-switchpane:contains('Review')",
- mobile: true,
- },
- {
- trigger: `.ticket-screen .refund-note:contains("${text}")`,
- run: () => {},
- },
- {
- content: "Go back to main TicketScreen when in mobile",
- trigger: ".pos-rightheader .floor-button",
- mobile: true,
- },
- ];
+ return adaptForMobile({
+ trigger: `.ticket-screen .refund-note:contains("${text}")`,
+ run: () => {},
+ });
}
tipContains(amount) {
return [
diff --git a/addons/point_of_sale/static/tests/tours/helpers/generic_components/OrderWidgetMethods.js b/addons/point_of_sale/static/tests/tours/helpers/generic_components/OrderWidgetMethods.js
new file mode 100644
index 00000000000..4fc811a8dfc
--- /dev/null
+++ b/addons/point_of_sale/static/tests/tours/helpers/generic_components/OrderWidgetMethods.js
@@ -0,0 +1,84 @@
+/** @odoo-module */
+
+import { negate } from "@point_of_sale/../tests/tours/helpers/utils";
+
+/**
+ * @typedef {{
+ * withClass?: string, // ex: withClass: ".selected.blue"
+ * withoutClass?: string,
+ * run?: function | string,
+ * productName?: string,
+ * quantity?: string,
+ * price?: string,
+ * customerNote?: string,
+ * comboParent?: string,
+ * }} LineOptions
+ */
+
+/**
+ * @param {LineOptions} options
+ * @returns {import("@web_tour/tour_service/tour_service").TourStep[]}
+ */
+export function hasLine({
+ withClass = "",
+ withoutClass = "",
+ run = () => {},
+ productName,
+ quantity,
+ price,
+ customerNote,
+ comboParent,
+} = {}) {
+ let trigger = `.order-container .orderline${withClass}`;
+ if (withoutClass) {
+ trigger += `:not(${withoutClass})`;
+ }
+ if (productName) {
+ trigger += `:has(.product-name:contains("${productName}"))`;
+ }
+ if (quantity) {
+ trigger += `:has(em.qty:contains("${quantity}"))`;
+ }
+ if (price) {
+ trigger += `:has(.price:contains("${price}"))`;
+ }
+ if (customerNote) {
+ trigger += `:has(.info-list .customer-note:contains("${customerNote}"))`;
+ }
+ if (comboParent) {
+ trigger += `:has(.info-list .combo-parent-name:contains("${comboParent}"))`;
+ }
+ const args = JSON.stringify(arguments[0]);
+ return [
+ {
+ content: `Check orderline with attributes: ${args}`,
+ trigger,
+ run: typeof run === "string" ? run : () => run(trigger),
+ },
+ ];
+}
+/**
+ * @param {LineOptions} options
+ * @returns {import("@web_tour/tour_service/tour_service").TourStep}
+ */
+export function doesNotHaveLine(options = {}) {
+ const step = hasLine(options)[0];
+ return [{ ...step, trigger: negate(step.trigger) }];
+}
+
+// TODO: there are instances where we have no selected orderline. Fix those instances
+
+export function hasTotal(amount) {
+ return {
+ content: `order total amount is '${amount}'`,
+ trigger: `.product-screen .order-summary .total:contains("${amount}")`,
+ isCheck: true,
+ };
+}
+export function hasTax(amount) {
+ return {
+ content: `order total tax is '${amount}'`,
+ trigger: `.order-summary .tax:contains("${amount}")`,
+ isCheck: true,
+ };
+}
diff --git a/addons/point_of_sale/static/tests/tours/helpers/utils.js b/addons/point_of_sale/static/tests/tours/helpers/utils.js
index 36af8efb16e..7619a9e4f56 100644
--- a/addons/point_of_sale/static/tests/tours/helpers/utils.js
+++ b/addons/point_of_sale/static/tests/tours/helpers/utils.js
@@ -143,3 +143,7 @@ export function createTourMethods(name, Do, Check = class {}, Execute = class {}
},
};
}
+
+export function negate(selector) {
+ return `body:not(:has(${selector}))`;
+}
diff --git a/addons/point_of_sale/static/tests/tours/pricelist_tour.js b/addons/point_of_sale/static/tests/tours/pricelist_tour.js
index 2e515684c50..2983f8fd219 100644
--- a/addons/point_of_sale/static/tests/tours/pricelist_tour.js
+++ b/addons/point_of_sale/static/tests/tours/pricelist_tour.js
@@ -6,6 +6,7 @@ import { ProductScreen } from "@point_of_sale/../tests/tours/helpers/ProductScre
import { roundDecimals as round_di } from "@web/core/utils/numbers";
import { nbsp } from "@web/core/utils/strings";
import { Numpad } from "./helpers/NumpadTourMethods";
+import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
function assert(condition, message) {
if (!condition) {
@@ -192,13 +193,7 @@ steps = steps.concat([
trigger: ".btn-switchpane:contains('Review')",
mobile: true,
},
- {
- trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf')",
- extra_trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf') ~ .info-list .info em:contains('1.0')",
- run: function () {},
- },
+ ...Order.hasLine({ productName: "Wall Shelf", quantity: "1.0", withClass: ".selected" }),
{
content: "click more button",
trigger: ".mobile-more-button",
@@ -213,19 +208,9 @@ steps = steps.concat([
trigger: ".selection-item:contains('min_quantity ordering')",
},
Numpad.click("2"),
- {
- content: "qty of Wall Shelf line should be 2",
- trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf')",
- extra_trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf') ~ .info-list .info em:contains('2.0')",
- run: function () {},
- },
- {
- content: "verify that unit price of shelf changed to $1",
- trigger: `.total > .value:contains('$${nbsp}2.00')`,
- run: function () {},
- },
+ ...Order.hasLine({ productName: "Wall Shelf", quantity: "2.0", withClass: ".selected" }),
+ // verify that unit price of shelf changed to $1
+ Order.hasTotal(`$${nbsp}2.00`),
{
content: "go back to the products",
trigger: ".floor-button",
@@ -240,24 +225,11 @@ steps = steps.concat([
trigger: ".btn-switchpane:contains('Review')",
mobile: true,
},
- {
- content: "Small Shelf line should be selected with quantity 1",
- trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf')",
- extra_trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf') ~ .info-list .info em:contains('1.0')",
- run: function () {},
- },
+ ...Order.hasLine({ productName: "Small Shelf", quantity: "1.0", withClass: ".selected" }),
Numpad.click("Price"),
Numpad.isActive("Price"),
Numpad.click("5"),
- {
- content: "Small Shelf line should be selected with unit price of 5",
- trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf')",
- extra_trigger:
- ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf') ~ .price:contains('5.0')",
- },
+ ...Order.hasLine({ productName: "Small Shelf", price: "5.0", withClass: ".selected" }),
Numpad.click("Qty"),
Numpad.isActive("Qty"),
{
@@ -273,11 +245,8 @@ steps = steps.concat([
content: "select public pricelist",
trigger: ".selection-item:contains('Public Pricelist')",
},
- {
- content:
- "verify that the boni shelf have been recomputed and the shelf have not (their price was manually overridden)",
- trigger: `.total > .value:contains('$${nbsp}8.96')`,
- },
+ // verify that the boni shelf have been recomputed and the shelf have not (their price was manually overridden)
+ Order.hasTotal(`$${nbsp}8.96`),
{
content: "click more button",
trigger: ".mobile-more-button",
diff --git a/addons/point_of_sale/static/tests/unit/pos_app_tests.js b/addons/point_of_sale/static/tests/unit/pos_app_tests.js
index 89c9e1e6e40..928c126140b 100644
--- a/addons/point_of_sale/static/tests/unit/pos_app_tests.js
+++ b/addons/point_of_sale/static/tests/unit/pos_app_tests.js
@@ -10,6 +10,22 @@ import { EventBus } from "@odoo/owl";
import { uiService } from "@web/core/ui/ui_service";
import { popupService } from "@point_of_sale/app/popup/popup_service";
+const mockContextualUtilsService = {
+ dependencies: ["pos", "localization"],
+ start(env, { pos, localization }) {
+ const formatCurrency = (value, hasSymbol = true) => {
+ return "dummy";
+ };
+ const floatIsZero = (value) => {
+ return value === 0;
+ };
+ env.utils = {
+ formatCurrency,
+ floatIsZero,
+ };
+ },
+};
+
QUnit.module("Chrome", {
beforeEach() {
registry
@@ -19,6 +35,7 @@ QUnit.module("Chrome", {
.add("barcode_reader", barcodeReaderService)
.add("ui", uiService)
.add("popup", popupService)
+ .add("contextual_utils_service", mockContextualUtilsService)
.add("barcode", {
start() {
return { bus: new EventBus() };
diff --git a/addons/pos_loyalty/static/src/css/Loyalty.scss b/addons/pos_loyalty/static/src/css/Loyalty.scss
index c5d9a3ee9f4..d2510f02104 100644
--- a/addons/pos_loyalty/static/src/css/Loyalty.scss
+++ b/addons/pos_loyalty/static/src/css/Loyalty.scss
@@ -63,7 +63,7 @@
}
}
-.pos .order {
+.pos .orderlines {
.active-programs {
padding-top: 0.5em;
@@ -78,10 +78,6 @@
font-weight: bold;
}
}
-
- .orderline.program-reward {
- font-style: italic;
- }
}
.pos-receipt .pos-coupon-rewards {
diff --git a/addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.js b/addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.js
deleted file mode 100644
index ac10b72b94e..00000000000
--- a/addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/** @odoo-module **/
-
-import { OrderSummary } from "@point_of_sale/app/screens/product_screen/order_summary/order_summary";
-import { patch } from "@web/core/utils/patch";
-
-patch(OrderSummary.prototype, {
- getLoyaltyPoints() {
- const order = this.pos.get_order();
- return order.getLoyaltyPoints();
- },
-});
diff --git a/addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.xml b/addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.xml
deleted file mode 100644
index e6750687181..00000000000
--- a/addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Points Won
- +
-
-
- Points Spent
- -
-
-
- New Total
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/pos_loyalty/static/src/overrides/components/orderline/orderline.js b/addons/pos_loyalty/static/src/overrides/components/orderline/orderline.js
deleted file mode 100644
index 39a3a51cf88..00000000000
--- a/addons/pos_loyalty/static/src/overrides/components/orderline/orderline.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/** @odoo-module **/
-
-import { Orderline } from "@point_of_sale/app/screens/product_screen/orderline/orderline";
-import { patch } from "@web/core/utils/patch";
-
-patch(Orderline.prototype, {
- get addedClasses() {
- return Object.assign(
- { "program-reward": this.props.line.is_reward_line },
- super.addedClasses
- );
- },
- _isGiftCardOrEWalletReward() {
- const coupon = this.pos.couponCache[this.props.line.coupon_id];
- if (coupon) {
- const program = this.pos.program_by_id[coupon.program_id];
- return (
- ["ewallet", "gift_card"].includes(program.program_type) &&
- this.props.line.is_reward_line
- );
- }
- return false;
- },
- _getGiftCardOrEWalletBalance() {
- const coupon = this.pos.couponCache[this.props.line.coupon_id];
- if (coupon) {
- return this.env.utils.formatCurrency(coupon.balance);
- }
- return this.env.utils.formatCurrency(0);
- },
-});
diff --git a/addons/pos_loyalty/static/src/overrides/components/orderline/orderline.xml b/addons/pos_loyalty/static/src/overrides/components/orderline/orderline.xml
deleted file mode 100644
index 9746ed05a4a..00000000000
--- a/addons/pos_loyalty/static/src/overrides/components/orderline/orderline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- !_isGiftCardOrEWalletReward()
-
-
-
-
-
-
diff --git a/addons/pos_loyalty/static/src/overrides/components/product_screen/product_screen.xml b/addons/pos_loyalty/static/src/overrides/components/product_screen/product_screen.xml
new file mode 100644
index 00000000000..e09c87aa5a7
--- /dev/null
+++ b/addons/pos_loyalty/static/src/overrides/components/product_screen/product_screen.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Current Balance:
+
+
+
+
+
+
+
+
+ Points Won
+ +
+
+
+ Points Spent
+ -
+
+
+ New Total
+
+
+
+
+
+
+
+
diff --git a/addons/pos_loyalty/static/src/overrides/models/orderline.js b/addons/pos_loyalty/static/src/overrides/models/orderline.js
new file mode 100644
index 00000000000..7952426a8f0
--- /dev/null
+++ b/addons/pos_loyalty/static/src/overrides/models/orderline.js
@@ -0,0 +1,35 @@
+/** @odoo-module */
+
+import { patch } from "@web/core/utils/patch";
+import { Orderline } from "@point_of_sale/app/store/models";
+
+patch(Orderline.prototype, {
+ /**
+ * @returns {boolean}
+ */
+ isGiftCardOrEWalletReward() {
+ const coupon = this.pos.couponCache[this.coupon_id];
+ if (!coupon || !this.is_reward_line) {
+ return false;
+ }
+ const program = this.pos.program_by_id[coupon.program_id];
+ return ["ewallet", "gift_card"].includes(program.program_type);
+ },
+ /**
+ * @returns {string}
+ */
+ getGiftCardOrEWalletBalance() {
+ const coupon = this.pos.couponCache[this.coupon_id];
+ return this.env.utils.formatCurrency(coupon?.balance || 0);
+ },
+ /**
+ * override
+ */
+ getDisplayClasses() {
+ return {
+ ...super.getDisplayClasses(),
+ "fst-italic":
+ this.pos.mainScreen.component.name === "ProductScreen" && this.is_reward_line,
+ };
+ },
+});
diff --git a/addons/pos_loyalty/static/tests/tours/GiftCardProgramTours.js b/addons/pos_loyalty/static/tests/tours/GiftCardProgramTours.js
index 2a367793936..75c01d6b779 100644
--- a/addons/pos_loyalty/static/tests/tours/GiftCardProgramTours.js
+++ b/addons/pos_loyalty/static/tests/tours/GiftCardProgramTours.js
@@ -8,96 +8,90 @@ import { registry } from "@web/core/registry";
import { TicketScreen } from "@point_of_sale/../tests/tours/helpers/TicketScreenTourMethods";
//#region GiftCardProgramCreateSetTour1
-registry
- .category("web_tour.tours")
- .add("GiftCardProgramCreateSetTour1", {
- test: true,
- url: "/pos/web",
- steps: () => {
- startSteps();
- ProductScreen.do.confirmOpeningPopup();
- ProductScreen.do.clickHomeCategory();
- ProductScreen.do.clickDisplayedProduct("Gift Card");
- PosLoyalty.check.orderTotalIs("50.00");
- PosLoyalty.exec.finalizeOrder("Cash", "50");
- return getSteps();
- }
- });
+registry.category("web_tour.tours").add("GiftCardProgramCreateSetTour1", {
+ test: true,
+ url: "/pos/web",
+ steps: () => {
+ startSteps();
+ ProductScreen.do.confirmOpeningPopup();
+ ProductScreen.do.clickHomeCategory();
+ ProductScreen.do.clickDisplayedProduct("Gift Card");
+ PosLoyalty.check.orderTotalIs("50.00");
+ PosLoyalty.exec.finalizeOrder("Cash", "50");
+ return getSteps();
+ },
+});
//#endregion
//#region GiftCardProgramCreateSetTour2
-registry
- .category("web_tour.tours")
- .add("GiftCardProgramCreateSetTour2", {
- test: true,
- url: "/pos/web",
- steps: () => {
-
- startSteps();
- ProductScreen.do.clickHomeCategory();
- ProductScreen.do.clickDisplayedProduct("Whiteboard Pen");
- PosLoyalty.do.enterCode("044123456");
- PosLoyalty.check.orderTotalIs("0.00");
- PosLoyalty.exec.finalizeOrder("Cash", "0");
- return getSteps();
- }
- });
+registry.category("web_tour.tours").add("GiftCardProgramCreateSetTour2", {
+ test: true,
+ url: "/pos/web",
+ steps: () => {
+ startSteps();
+ ProductScreen.do.clickHomeCategory();
+ ProductScreen.do.clickDisplayedProduct("Whiteboard Pen");
+ PosLoyalty.do.enterCode("044123456");
+ PosLoyalty.check.orderTotalIs("0.00");
+ PosLoyalty.exec.finalizeOrder("Cash", "0");
+ return getSteps();
+ },
+});
//#endregion
//#region GiftCardProgramScanUseTour
-registry
- .category("web_tour.tours")
- .add("GiftCardProgramScanUseTour", {
- test: true,
- url: "/pos/web",
- steps: () => {
- startSteps();
- ProductScreen.do.confirmOpeningPopup();
- ProductScreen.do.clickHomeCategory();
- // Pay the 5$ gift card.
- ProductScreen.do.clickDisplayedProduct("Gift Card");
- TextInputPopup.check.isShown();
- TextInputPopup.do.inputText("044123456");
- TextInputPopup.do.clickConfirm();
- PosLoyalty.check.orderTotalIs("5.00");
- PosLoyalty.exec.finalizeOrder("Cash", "5");
- // Partially use the gift card. (4$)
- ProductScreen.exec.addOrderline("Desk Pad", "2", "2", "4.0");
- PosLoyalty.do.enterCode("044123456");
- PosLoyalty.check.orderTotalIs("0.00");
- PosLoyalty.exec.finalizeOrder("Cash", "0");
- // Use the remaining of the gift card. (5$ - 4$ = 1$)
- ProductScreen.exec.addOrderline("Whiteboard Pen", "6", "6", "36.0");
- PosLoyalty.do.enterCode("044123456");
- PosLoyalty.check.orderTotalIs("35.00");
- PosLoyalty.exec.finalizeOrder("Cash", "35");
- return getSteps();
- }
- });
+registry.category("web_tour.tours").add("GiftCardProgramScanUseTour", {
+ test: true,
+ url: "/pos/web",
+ steps: () => {
+ startSteps();
+ ProductScreen.do.confirmOpeningPopup();
+ ProductScreen.do.clickHomeCategory();
+ // Pay the 5$ gift card.
+ ProductScreen.do.clickDisplayedProduct("Gift Card");
+ TextInputPopup.check.isShown();
+ TextInputPopup.do.inputText("044123456");
+ TextInputPopup.do.clickConfirm();
+ PosLoyalty.check.orderTotalIs("5.00");
+ PosLoyalty.exec.finalizeOrder("Cash", "5");
+ // Partially use the gift card. (4$)
+ ProductScreen.exec.addOrderline("Desk Pad", "2", "2", "4.0");
+ PosLoyalty.do.enterCode("044123456");
+ PosLoyalty.check.orderTotalIs("0.00");
+ PosLoyalty.exec.finalizeOrder("Cash", "0");
+ // Use the remaining of the gift card. (5$ - 4$ = 1$)
+ ProductScreen.exec.addOrderline("Whiteboard Pen", "6", "6", "36.0");
+ PosLoyalty.do.enterCode("044123456");
+ PosLoyalty.check.orderTotalIs("35.00");
+ PosLoyalty.exec.finalizeOrder("Cash", "35");
+ return getSteps();
+ },
+});
//#endregion
-registry
- .category("web_tour.tours")
- .add("GiftCardWithRefundtTour", {
- test: true,
- url: "/pos/web",
- steps: () => {
- startSteps();
- ProductScreen.do.confirmOpeningPopup();
- ProductScreen.do.clickHomeCategory();
- ProductScreen.exec.addOrderline("Magnetic Board", "1"); // 1.98
- PosLoyalty.check.orderTotalIs("1.98");
- PosLoyalty.exec.finalizeOrder("Cash", "20");
- ProductScreen.do.clickRefund();
- TicketScreen.do.selectOrder("-0001");
- TicketScreen.do.clickOrderline("Magnetic Board");
- ProductScreen.do.pressNumpad("1");
- TicketScreen.do.confirmRefund();
- ProductScreen.check.isShown();
- ProductScreen.check.selectedOrderlineHas("Magnetic Board", "-1.00");
- ProductScreen.exec.addOrderline("Gift Card", "1");
- ProductScreen.check.selectedOrderlineHas("Gift Card", "1");
- PosLoyalty.check.orderTotalIs("0.0");
- return getSteps();
- }
- });
+registry.category("web_tour.tours").add("GiftCardWithRefundtTour", {
+ test: true,
+ url: "/pos/web",
+ steps: () => {
+ startSteps();
+ ProductScreen.do.confirmOpeningPopup();
+ ProductScreen.do.clickHomeCategory();
+ ProductScreen.exec.addOrderline("Magnetic Board", "1"); // 1.98
+ PosLoyalty.check.orderTotalIs("1.98");
+ PosLoyalty.exec.finalizeOrder("Cash", "20");
+ ProductScreen.do.clickRefund();
+ TicketScreen.do.selectOrder("-0001");
+ TicketScreen.check.hasLine({
+ withClass: ".selected",
+ productName: "Magnetic Board",
+ });
+ ProductScreen.do.pressNumpad("1");
+ TicketScreen.do.confirmRefund();
+ ProductScreen.check.isShown();
+ ProductScreen.check.selectedOrderlineHas("Magnetic Board", "-1.00");
+ ProductScreen.exec.addOrderline("Gift Card", "1");
+ ProductScreen.check.selectedOrderlineHas("Gift Card", "1");
+ PosLoyalty.check.orderTotalIs("0.0");
+ return getSteps();
+ },
+});
diff --git a/addons/pos_loyalty/static/tests/tours/PosLoyaltyTourMethods.js b/addons/pos_loyalty/static/tests/tours/PosLoyaltyTourMethods.js
index 7e52346f790..455b939e2b7 100644
--- a/addons/pos_loyalty/static/tests/tours/PosLoyaltyTourMethods.js
+++ b/addons/pos_loyalty/static/tests/tours/PosLoyaltyTourMethods.js
@@ -5,6 +5,7 @@ import { Do as ProductScreenDo } from "@point_of_sale/../tests/tours/helpers/Pro
import { Do as PaymentScreenDo } from "@point_of_sale/../tests/tours/helpers/PaymentScreenTourMethods";
import { Do as ReceiptScreenDo } from "@point_of_sale/../tests/tours/helpers/ReceiptScreenTourMethods";
import { Do as ChromeDo } from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods";
+import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
const ProductScreen = { do: new ProductScreenDo() };
const PaymentScreen = { do: new PaymentScreenDo() };
@@ -14,15 +15,16 @@ const Chrome = { do: new ChromeDo() };
class Do {
selectRewardLine(rewardName) {
return [
- {
- content: "select reward line",
- trigger: `.orderline.program-reward .product-name:contains("${rewardName}")`,
- },
- {
- content: "check reward line if selected",
- trigger: `.orderline.selected.program-reward .product-name:contains("${rewardName}")`,
- run: function () {}, // it's a check
- },
+ ...Order.hasLine({
+ withClass: ".fst-italic",
+ withoutClass: ".selected",
+ run: "click",
+ productName: rewardName,
+ }),
+ ...Order.hasLine({
+ withClass: ".selected.fst-italic",
+ productName: rewardName,
+ }),
];
}
enterCode(code) {
@@ -99,35 +101,15 @@ class Do {
class Check {
hasRewardLine(rewardName, amount, qty) {
- const steps = [
- {
- content: "check if reward line is there",
- trigger: `.orderline.program-reward span.product-name:contains("${rewardName}")`,
- run: function () {},
- },
- {
- content: "check if the reward price is correct",
- trigger: `.orderline.program-reward span.price:contains("${amount}")`,
- run: function () {},
- },
- ];
- if (qty) {
- steps.push({
- content: "check if the reward qty is correct",
- trigger: `.order .orderline.program-reward .product-name:contains("${rewardName}") ~ .info-list em:contains("${qty}")`,
- run: function () {},
- });
- }
- return steps;
+ return Order.hasLine({
+ withClass: ".fst-italic",
+ productName: rewardName,
+ price: amount,
+ quantity: qty,
+ });
}
orderTotalIs(total_str) {
- return [
- {
- content: "order total contains " + total_str,
- trigger: '.order .total .value:contains("' + total_str + '")',
- run: function () {}, // it's a check
- },
- ];
+ return [Order.hasTotal(total_str)];
}
checkNoClaimableRewards() {
return [
diff --git a/addons/pos_restaurant/static/src/app/product_screen/order/order.js b/addons/pos_restaurant/static/src/app/product_screen/order/order.js
deleted file mode 100644
index 19540315342..00000000000
--- a/addons/pos_restaurant/static/src/app/product_screen/order/order.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/** @odoo-module */
-
-import { patch } from "@web/core/utils/patch";
-import { OrderWidget } from "@point_of_sale/app/screens/product_screen/order/order";
-import { AlertPopup } from "@point_of_sale/app/utils/alert_popup/alert_popup";
-import { _t } from "@web/core/l10n/translation";
-
-patch(OrderWidget.prototype, {
- releaseTable() {
- const currentTable = this.pos.table;
- const orderOnTable = this.pos.orders.filter(
- (o) => o.tableId === currentTable.id && o.finalized === false
- );
- const orderOnTableWithOrderline = orderOnTable.find((o) => o.orderlines.length > 0);
-
- if (orderOnTableWithOrderline) {
- this.popup.add(AlertPopup, {
- title: _t("Open orders"),
- body: _t(
- "There are open orders on this table. Please close them before releasing the table."
- ),
- });
-
- return;
- }
-
- for (const order of orderOnTable) {
- this.pos.removeOrder(order);
- }
-
- this.pos.showScreen("FloorScreen");
- },
- get showReleaseBtn() {
- if (!this.pos.config.module_pos_restaurant) {
- return false;
- }
-
- const currentTable = this.pos.table;
- const noEmptyOrderOnTable = this.pos.orders.filter(
- (o) => o.tableId === currentTable.id && o.finalized === false && o.orderlines.length > 0
- );
- return noEmptyOrderOnTable.length ? false : true;
- },
-});
diff --git a/addons/pos_restaurant/static/src/app/product_screen/order/order.xml b/addons/pos_restaurant/static/src/app/product_screen/order/order.xml
deleted file mode 100644
index 6558986b3d4..00000000000
--- a/addons/pos_restaurant/static/src/app/product_screen/order/order.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- Release table
-
-
-
diff --git a/addons/pos_restaurant/static/src/app/product_screen/product_screen.js b/addons/pos_restaurant/static/src/app/product_screen/product_screen.js
new file mode 100644
index 00000000000..a80d9846cad
--- /dev/null
+++ b/addons/pos_restaurant/static/src/app/product_screen/product_screen.js
@@ -0,0 +1,25 @@
+/** @odoo-module */
+
+import { patch } from "@web/core/utils/patch";
+import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
+
+patch(ProductScreen.prototype, {
+ releaseTable() {
+ const orderOnTable = this.pos.orders.filter(
+ (o) => o.tableId === this.pos.table.id && o.finalized === false
+ );
+ for (const order of orderOnTable) {
+ this.pos.removeOrder(order);
+ }
+ this.pos.showScreen("FloorScreen");
+ },
+ showReleaseBtn() {
+ return (
+ this.pos.config.module_pos_restaurant &&
+ !this.pos.orders.some(
+ (o) =>
+ o.tableId === this.pos.table.id && o.finalized === false && o.orderlines.length
+ )
+ );
+ },
+});
diff --git a/addons/pos_restaurant/static/src/app/product_screen/product_screen.xml b/addons/pos_restaurant/static/src/app/product_screen/product_screen.xml
new file mode 100644
index 00000000000..2a30ce8973d
--- /dev/null
+++ b/addons/pos_restaurant/static/src/app/product_screen/product_screen.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ Release table
+
+
+
diff --git a/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js b/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js
index 90bcfe931ad..3f14bd7bce4 100644
--- a/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js
+++ b/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.js
@@ -2,15 +2,16 @@
import { Order } from "@point_of_sale/app/store/models";
-import { SplitOrderline } from "@pos_restaurant/app/split_bill_screen/split_orderline/split_orderline";
import { registry } from "@web/core/registry";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { Component, useState } from "@odoo/owl";
+import { Orderline } from "@point_of_sale/app/generic_components/orderline/orderline";
+import { OrderWidget } from "@point_of_sale/app/generic_components/order_widget/order_widget";
import { groupBy } from "@web/core/utils/arrays";
export class SplitBillScreen extends Component {
static template = "pos_restaurant.SplitBillScreen";
- static components = { SplitOrderline };
+ static components = { Orderline, OrderWidget };
setup() {
this.pos = usePos();
@@ -38,6 +39,13 @@ export class SplitBillScreen extends Component {
this._updateNewOrder(l);
}
}
+ getLineData(line) {
+ const splitQty = this.splitlines[line.id].quantity;
+ if (!splitQty) {
+ return line.getDisplayData();
+ }
+ return { ...line.getDisplayData(), qty: `${splitQty} / ${line.get_quantity_str()}` };
+ }
back() {
this.pos.showScreen("ProductScreen");
}
diff --git a/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.xml b/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.xml
index cfbdfb1be37..f6e3e5d9a37 100644
--- a/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.xml
+++ b/addons/pos_restaurant/static/src/app/split_bill_screen/split_bill_screen.xml
@@ -13,20 +13,17 @@
Bill Splitting