From 60f41b1f831248c58f554dbe40114ef0d4ce42c0 Mon Sep 17 00:00:00 2001 From: "Adrien Guilliams (adgu)" Date: Mon, 27 Mar 2023 15:03:41 +0000 Subject: [PATCH] [IMP] pos: Change waiter screen The PoS needed improvments for the mobile use. This commit is changing the display of some screen and popups like the NumberPopup. It also changes the logic of the order count badge on the table. It now displays the number of orderline (and their quantity) to send to the printers or preparation display if there is at least one. closes odoo/odoo#121286 Related: odoo/enterprise#41013 Signed-off-by: Monnom David (moda) --- .../static/src/app/navbar/BackButton.js | 19 ++ .../static/src/app/navbar/navbar.js | 6 + .../static/src/app/navbar/navbar.xml | 5 +- .../static/src/app/number_buffer_service.js | 5 - .../point_of_sale/static/src/app/pos_store.js | 9 + .../static/src/js/Popups/NumberPopup.js | 31 +- .../src/js/Popups/ProductConfiguratorPopup.js | 32 +- .../js/Screens/PaymentScreen/PaymentScreen.js | 38 ++- .../PaymentScreenPaymentLines.js | 29 +- .../src/js/Screens/ProductScreen/Orderline.js | 1 - .../js/Screens/ProductScreen/ProductScreen.js | 45 ++- addons/point_of_sale/static/src/js/models.js | 40 ++- addons/point_of_sale/static/src/scss/pos.scss | 275 ++++++++++++------ .../src/xml/ChromeWidgets/BackButton.xml | 14 + .../static/src/xml/Popups/NumberPopup.xml | 18 +- .../xml/Popups/ProductConfiguratorPopup.xml | 95 ++++-- .../Screens/PaymentScreen/PaymentScreen.xml | 20 +- .../PaymentScreenPaymentLines.xml | 45 ++- .../Screens/ProductScreen/ActionpadWidget.xml | 7 +- .../Screens/ProductScreen/ProductScreen.xml | 37 +-- .../Screens/ReceiptScreen/ReceiptScreen.xml | 22 +- .../static/tests/tours/Chrome.tour.js | 2 +- .../static/tests/tours/PaymentScreen.tour.js | 20 +- .../static/tests/tours/ReceiptScreen.tour.js | 5 +- .../static/tests/tours/TicketScreen.tour.js | 3 + .../static/tests/tours/acceptance_tour.js | 59 +++- .../tours/helpers/NumberPopupTourMethods.js | 32 +- .../tours/helpers/PaymentScreenTourMethods.js | 57 +++- .../tours/helpers/ProductScreenTourMethods.js | 41 ++- .../tours/helpers/ReceiptScreenTourMethods.js | 6 + .../static/tests/tours/pricelist_tour.js | 16 +- addons/pos_hr/static/tests/tours/PosHrTour.js | 9 +- addons/pos_loyalty/static/src/js/Loyalty.js | 2 +- addons/pos_restaurant/models/pos_config.py | 26 +- .../src/app/floor_screen/floor_screen.js | 33 ++- .../static/src/app/floor_screen/table.js | 52 ++-- .../static/src/app/navbar/BackButton.js | 58 ++++ .../src/app/navbar/BackToFloorButton.js | 26 -- .../static/src/app/navbar/navbar.js | 5 +- .../static/src/app/navbar/navbar.xml | 3 - .../static/src/app/pos_store.js | 13 +- .../static/src/js/Screens/PaymentScreen.js | 4 - .../Screens/ProductScreen/ActionpadWidget.js | 2 +- .../ControlButtons/PrintBillButton.js | 3 + .../js/Screens/ProductScreen/ProductScreen.js | 45 ++- .../js/Screens/ReceiptScreen/ReceiptScreen.js | 4 - .../static/src/js/Screens/TipScreen.js | 1 - addons/pos_restaurant/static/src/js/models.js | 12 +- .../static/src/scss/restaurant.scss | 18 -- .../xml/ChromeWidgets/BackToFloorButton.xml | 21 -- .../Screens/ProductScreen/ProductScreen.xml | 24 ++ .../static/tests/tours/ControlButtons.tour.js | 4 +- .../static/tests/tours/FloorScreen.tour.js | 5 +- .../static/tests/tours/TicketScreen.tour.js | 4 +- .../static/tests/tours/TipScreen.tour.js | 12 +- .../tests/tours/helpers/ChromeTourMethods.js | 12 +- .../tours/helpers/ProductScreenTourMethods.js | 18 ++ .../static/tests/tours/pos_restaurant.js | 6 +- addons/pos_restaurant/tests/test_frontend.py | 8 + 59 files changed, 1046 insertions(+), 418 deletions(-) create mode 100644 addons/point_of_sale/static/src/app/navbar/BackButton.js create mode 100644 addons/point_of_sale/static/src/xml/ChromeWidgets/BackButton.xml create mode 100644 addons/pos_restaurant/static/src/app/navbar/BackButton.js delete mode 100644 addons/pos_restaurant/static/src/app/navbar/BackToFloorButton.js delete mode 100644 addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml create mode 100644 addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ProductScreen.xml diff --git a/addons/point_of_sale/static/src/app/navbar/BackButton.js b/addons/point_of_sale/static/src/app/navbar/BackButton.js new file mode 100644 index 00000000000..3f67cb88677 --- /dev/null +++ b/addons/point_of_sale/static/src/app/navbar/BackButton.js @@ -0,0 +1,19 @@ +/** @odoo-module */ + +import { Component, useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { usePos } from "@point_of_sale/app/pos_hook"; + +export class BackButton extends Component { + static template = "BackButton"; + + setup() { + super.setup(); + this.pos = usePos(); + this.ui = useState(useService("ui")); + } + async backToFloorScreen() { + this.pos.globalState.mobile_pane = "right"; + this.pos.showScreen("ProductScreen"); + } +} diff --git a/addons/point_of_sale/static/src/app/navbar/navbar.js b/addons/point_of_sale/static/src/app/navbar/navbar.js index 5e793d75003..814309dbdde 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.js +++ b/addons/point_of_sale/static/src/app/navbar/navbar.js @@ -10,6 +10,7 @@ import { SaleDetailsButton } from "@point_of_sale/js/ChromeWidgets/SaleDetailsBu import { SyncNotification } from "@point_of_sale/js/ChromeWidgets/SyncNotification"; import { CashMovePopup } from "./cash_move_popup/cash_move_popup"; import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScreen"; +import { BackButton } from "@point_of_sale/app/navbar/BackButton"; import { Component, useState, useExternalListener } from "@odoo/owl"; import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup"; @@ -22,6 +23,7 @@ export class Navbar extends Component { ProxyStatus, SaleDetailsButton, SyncNotification, + BackButton, }; static props = { showCashMoveButton: Boolean, @@ -101,4 +103,8 @@ export class Navbar extends Component { const info = await this.pos.globalState.getClosePosInfo(); this.popup.add(ClosePosPopup, { info, keepBehind: true }); } + + showBackButton() { + return this.pos.showBackButton() && this.ui.isSmall; + } } diff --git a/addons/point_of_sale/static/src/app/navbar/navbar.xml b/addons/point_of_sale/static/src/app/navbar/navbar.xml index 8ee47f6643d..decbfe16f96 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.xml +++ b/addons/point_of_sale/static/src/app/navbar/navbar.xml @@ -4,10 +4,11 @@
-
- +
+
+
diff --git a/addons/point_of_sale/static/src/app/number_buffer_service.js b/addons/point_of_sale/static/src/app/number_buffer_service.js index e28972ac221..d2c7be2f0f1 100644 --- a/addons/point_of_sale/static/src/app/number_buffer_service.js +++ b/addons/point_of_sale/static/src/app/number_buffer_service.js @@ -313,11 +313,6 @@ class NumberBuffer extends EventBus { // it should not be in a start the buffer over state anymore. this.state.toStartOver = false; - if (this.config.maxValue && this.state.buffer > this.config.maxValue) { - this.state.buffer = this.config.maxValue.toString(); - this.config.maxValueReached(); - } - this.trigger("buffer-update", this.state.buffer); } } diff --git a/addons/point_of_sale/static/src/app/pos_store.js b/addons/point_of_sale/static/src/app/pos_store.js index 04292576d89..21ba4f59e3e 100644 --- a/addons/point_of_sale/static/src/app/pos_store.js +++ b/addons/point_of_sale/static/src/app/pos_store.js @@ -9,6 +9,8 @@ import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { _t } from "@web/core/l10n/translation"; import { CashOpeningPopup } from "@point_of_sale/js/Popups/CashOpeningPopup"; import { sprintf } from "@web/core/utils/strings"; +import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen"; +import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; export class PosStore extends Reactive { hasBigScrollBars = false; @@ -283,6 +285,13 @@ export class PosStore extends Reactive { false ); } + + showBackButton() { + return ( + this.mainScreen.component === PaymentScreen || + (this.mainScreen.component === ProductScreen && this.globalState.mobile_pane == "left") + ); + } } export const posService = { diff --git a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js index 3a43abac25b..0c48e37162b 100644 --- a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js @@ -3,7 +3,7 @@ import { _t } from "@web/core/l10n/translation"; import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { useService } from "@web/core/utils/hooks"; -import { useState } from "@odoo/owl"; +import { useState, useRef, onMounted } from "@odoo/owl"; export class NumberPopup extends AbstractAwaitablePopup { static template = "NumberPopup"; @@ -16,6 +16,7 @@ export class NumberPopup extends AbstractAwaitablePopup { cheap: false, startingValue: null, isPassword: false, + nbrDecimal: 0, inputSuffix: "", getInputBufferReminder: () => false, }; @@ -34,18 +35,32 @@ export class NumberPopup extends AbstractAwaitablePopup { super.setup(); this.localization = useService("localization"); let startingBuffer = ""; + let startingPayload = null; if (typeof this.props.startingValue === "number" && this.props.startingValue > 0) { startingBuffer = this.props.startingValue + .toFixed(this.props.nbrDecimal) .toString() .replace(".", this.decimalSeparator); + startingPayload = this.props.startingValue.toFixed(this.props.nbrDecimal); } - this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected }); + this.state = useState({ + buffer: startingBuffer, + toStartOver: this.props.isInputSelected, + payload: startingPayload, + }); this.numberBuffer = useService("number_buffer"); this.numberBuffer.use({ triggerAtEnter: () => this.confirm(), triggerAtEscape: () => this.cancel(), state: this.state, }); + this.inputRef = useRef("input"); + onMounted(this.onMounted); + } + onMounted() { + if (this.inputRef.el) { + this.inputRef.el.focus(); + } } get decimalSeparator() { return this.localization.decimalPoint; @@ -61,7 +76,7 @@ export class NumberPopup extends AbstractAwaitablePopup { } } confirm(event) { - if (this.numberBuffer.get()) { + if (this.numberBuffer.get() || this.state.payload) { super.confirm(); } } @@ -69,6 +84,16 @@ export class NumberPopup extends AbstractAwaitablePopup { this.numberBuffer.sendKey(key); } getPayload() { + let startingPayload = null; + if (typeof this.props.startingValue === "number" && this.props.startingValue > 0) { + startingPayload = this.props.startingValue.toFixed(this.props.nbrDecimal); + } + if (this.state.payload != startingPayload) { + return this.state.payload; + } return this.numberBuffer.get(); } + isMobile() { + return window.innerWidth <= 768; + } } diff --git a/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js b/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js index ca3e876ca61..2f01b335932 100644 --- a/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js @@ -1,12 +1,11 @@ /** @odoo-module */ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { Component, useRef, useState, useSubEnv } from "@odoo/owl"; -import { usePos } from "@point_of_sale/app/pos_hook"; +import { useService } from "@web/core/utils/hooks"; export class BaseProductAttribute extends Component { setup() { super.setup(); - this.pos = usePos(); this.env.attribute_components.push(this); this.attribute = this.props.attribute; this.values = this.attribute.values; @@ -68,11 +67,16 @@ export class ProductConfiguratorPopup extends AbstractAwaitablePopup { setup() { super.setup(); useSubEnv({ attribute_components: [] }); + this.state = useState({ + quantity: 1, + }); + this.ui = useService("ui"); } getPayload() { var selected_attributes = []; var price_extra = 0.0; + const quantity = this.state.quantity; this.env.attribute_components.forEach((attribute_component) => { const { value, extra } = attribute_component.getValue(); @@ -80,9 +84,33 @@ export class ProductConfiguratorPopup extends AbstractAwaitablePopup { price_extra += extra; }); + if (quantity > 1) { + return { + selected_attributes, + price_extra, + quantity, + }; + } + return { selected_attributes, price_extra, }; } + get imageUrl() { + const product = this.props.product; + return `/web/image?model=product.product&field=image_128&id=${product.id}&unique=${product.write_date}`; + } + get unitPrice() { + return this.env.utils.formatCurrency(this.props.product.lst_price); + } + addOneQuantity() { + ++this.state.quantity; + } + removeOneQuantity() { + if (this.state.quantity == 1) { + return; + } + --this.state.quantity; + } } diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index da48104c145..8d9d424cb66 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -59,15 +59,6 @@ export class PaymentScreen extends Component { // Note that the component listens to it. triggerAtInput: () => this.updateSelectedPaymentline(), }; - // Check if pos has a cash payment method - const hasCashPaymentMethod = this.payment_methods_from_config.some( - (method) => method.type === "cash" - ); - - if (!hasCashPaymentMethod) { - config["maxValue"] = this.currentOrder.get_due(); - config["maxValueReached"] = this.showMaxValueError.bind(this); - } return config; } @@ -109,25 +100,47 @@ export class PaymentScreen extends Component { return false; } } - updateSelectedPaymentline() { + updateSelectedPaymentline(amount = false) { if (this.paymentLines.every((line) => line.paid)) { this.currentOrder.add_paymentline(this.payment_methods_from_config[0]); } if (!this.selectedPaymentLine) { return; } // do nothing if no selected payment line + if (amount === false) { + if (this.numberBuffer.get() === null) { + amount = null; + } else if (this.numberBuffer.get() === "") { + amount = 0; + } else { + amount = this.numberBuffer.getFloat(); + } + } // disable changing amount on paymentlines with running or done payments on a payment terminal const payment_terminal = this.selectedPaymentLine.payment_method.payment_terminal; + const hasCashPaymentMethod = this.payment_methods_from_config.some( + (method) => method.type === "cash" + ); + if ( + !hasCashPaymentMethod && + this.currentOrder.get_due() + this.selectedPaymentLine.amount > 0 && + amount > this.currentOrder.get_due() + this.selectedPaymentLine.amount + ) { + this.selectedPaymentLine.set_amount(0); + this.numberBuffer.set(this.currentOrder.get_due().toString()); + amount = this.currentOrder.get_due(); + this.showMaxValueError(); + } if ( payment_terminal && !["pending", "retry"].includes(this.selectedPaymentLine.get_payment_status()) ) { return; } - if (this.numberBuffer.get() === null) { + if (amount === null) { this.deletePaymentLine(this.selectedPaymentLine.cid); } else { - this.selectedPaymentLine.set_amount(this.numberBuffer.getFloat()); + this.selectedPaymentLine.set_amount(amount); } } toggleIsToInvoice() { @@ -146,6 +159,7 @@ export class PaymentScreen extends Component { title: tip ? this.env._t("Change Tip") : this.env._t("Add Tip"), startingValue: value, isInputSelected: true, + nbrDecimal: this.pos.globalState.currency.decimal_places, inputSuffix: this.pos.globalState.currency.symbol, }); diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js index dfd0e867514..d8f24df7f0d 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js @@ -1,10 +1,20 @@ /** @odoo-module */ -import { Component } from "@odoo/owl"; +import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup"; +import { useService } from "@web/core/utils/hooks"; +import { Component, useState } from "@odoo/owl"; +import { usePos } from "@point_of_sale/app/pos_hook"; export class PaymentScreenPaymentLines extends Component { static template = "PaymentScreenPaymentLines"; + setup() { + super.setup(); + this.ui = useState(useService("ui")); + this.popup = useService("popup"); + this.pos = usePos(); + } + formatLineAmount(paymentline) { return this.env.utils.formatCurrency(paymentline.get_amount(), false); } @@ -14,4 +24,21 @@ export class PaymentScreenPaymentLines extends Component { unselectedLineClass(line) { return {}; } + async selectLine(paymentline) { + this.props.selectLine(paymentline.cid); + + if (this.ui.isSmall) { + const { confirmed, payload } = await this.popup.add(NumberPopup, { + title: this.env._t("New amount"), + startingValue: parseFloat(paymentline.amount), + isInputSelected: true, + nbrDecimal: this.pos.globalState.currency.decimal_places, + }); + + if (confirmed) { + this.props.updateSelectedPaymentline(parseFloat(payload)); + } + } + return; + } } diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js index d1391274170..fe9b763024d 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js @@ -15,7 +15,6 @@ export class Orderline extends Component { setup() { this.pos = usePos(); } - selectLine() { const line = this.props.line; // the orderline if (this.pos.globalState.get_order().selected_orderline.id !== line.id) { diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js index 04eae8674c9..db63d02dada 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js @@ -13,7 +13,7 @@ import { ControlButtonPopup } from "@point_of_sale/js/Popups/ControlButtonPopup" import { ConnectionLostError } from "@web/core/network/rpc_service"; import { usePos } from "@point_of_sale/app/pos_hook"; -import { Component, onMounted, useState } from "@odoo/owl"; +import { Component, onMounted, useState, useRef } from "@odoo/owl"; import { ErrorBarcodePopup } from "@point_of_sale/js/Popups/ErrorBarcodePopup"; import { NumpadWidget } from "./NumpadWidget"; @@ -39,6 +39,7 @@ export class ProductScreen extends ControlButtonsMixin(Component) { this.orm = useService("orm"); this.notification = useService("pos_notification"); this.numberBuffer = useService("number_buffer"); + this.reminderRef = useRef("reminder"); onMounted(this.onMounted); useBarcodeReader({ @@ -49,10 +50,6 @@ export class ProductScreen extends ControlButtonsMixin(Component) { discount: this._barcodeDiscountAction, }); - this.state = useState({ - mobile_pane: this.props.mobile_pane || "right", - }); - // Call `resset` 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. @@ -198,7 +195,7 @@ export class ProductScreen extends ControlButtonsMixin(Component) { last_orderline.set_discount(code.value); } } - async _displayAllControlPopup() { + async displayAllControlPopup() { await this.popup.add(ControlButtonPopup, { controlButtons: this.controlButtons, }); @@ -233,6 +230,40 @@ export class ProductScreen extends ControlButtonsMixin(Component) { order.add_orderline(newLine); } } + get selectedOrderlineQuantity() { + return this.currentOrder.get_selected_orderline()?.get_quantity_str(); + } + get selectedOrderlineDisplayName() { + return this.currentOrder.get_selected_orderline()?.get_full_product_name(); + } + get selectedOrderlineTotal() { + return this.env.utils.formatCurrency( + this.currentOrder.get_selected_orderline()?.get_display_price() + ); + } + /** + * This getter is used to restart the animation on the product-reminder. + * When the information present on the product-reminder will change, + * the key will change and thus a new product-reminder will be created + * and the old one will be garbage collected leading to the animation + * being retriggered. + */ + get animationKey() { + return [ + this.selectedOrderlineQuantity, + this.selectedOrderlineDisplayName, + this.selectedOrderlineTotal, + ].join(","); + } + get showProductReminder() { + return this.currentOrder.get_selected_orderline() && this.selectedOrderlineQuantity; + } + primaryPayButton() { + return !this.currentOrder.is_empty(); + } + primaryReviewButton() { + return !this.primaryPayButton() && !this.currentOrder.is_empty(); + } // FIXME POSREF this is dead code, check if we need the business logic that's left in here // If we do it should be in the model. async onClickPay() { @@ -266,7 +297,7 @@ export class ProductScreen extends ControlButtonsMixin(Component) { this.currentOrder.pay(); } switchPane() { - this.state.mobile_pane = this.state.mobile_pane === "left" ? "right" : "left"; + this.pos.globalState.switchPane(); } } diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 30816127b6a..a9339ed6ada 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -167,6 +167,7 @@ export class PosGlobalState extends PosModel { this.validated_orders_name_server_id_map = {}; this.numpadMode = "quantity"; + this.mobile_pane = "right"; // Record this.toRefundLines = {}; @@ -1528,6 +1529,9 @@ export class PosGlobalState extends PosModel { isOpenOrderShareable() { return this.config.trusted_config_ids.length > 0; } + switchPane() { + this.mobile_pane = this.mobile_pane === "left" ? "right" : "left"; + } } PosGlobalState.prototype.electronic_payment_interfaces = {}; @@ -1580,6 +1584,7 @@ export class Product extends PosModel { async getAddProductOptions(base_code) { let price_extra = 0.0; let draftPackLotLines, weight, description, packLotLinesToEdit; + let quantity = 1; if (this.attribute_line_ids.some((id) => id in this.pos.attributes_by_ptal_id)) { const attributes = this.attribute_line_ids @@ -1596,6 +1601,9 @@ export class Product extends PosModel { if (confirmed) { description = payload.selected_attributes.join(", "); price_extra += payload.price_extra; + if (payload.quantity) { + quantity = payload.quantity; + } } else { return; } @@ -1667,7 +1675,7 @@ export class Product extends PosModel { weight = this.pos.db.product_packaging_by_barcode[base_code.code].qty; } - return { draftPackLotLines, quantity: weight, description, price_extra }; + return { draftPackLotLines, quantity, weight, description, price_extra }; } isPricelistItemUsable(item, date) { const categories = this.parent_category_ids.concat(this.categ.id); @@ -1803,7 +1811,7 @@ export class Orderline extends PosModel { this.product = options.product; this.tax_ids = options.tax_ids; this.set_product_lot(this.product); - this.set_quantity(1); + options.quantity ? this.set_quantity(options.quantity) : this.set_quantity(1); this.discount = 0; this.note = ""; this.hasChange = false; @@ -3069,12 +3077,13 @@ export class Order extends PosModel { * it uses the variable lastOrderPrepaChange which contains the last changes sent * to perform this calculation. */ - getOrderChanges() { + getOrderChanges(skipped = false) { const prepaCategoryIds = this.pos.orderPreparationCategories; const oldChanges = this.lastOrderPrepaChange; const changes = {}; + let changesCount = 0; - if (!prepaCategoryIds.size) { + if (!skipped && !prepaCategoryIds.size) { return {}; } @@ -3083,10 +3092,6 @@ export class Order extends PosModel { for (const orderlineIdx in this.orderlines) { const orderline = this.orderlines[orderlineIdx]; - if (orderline.skipChange || orderline.refunded_orderline_id) { - continue; - } - const product = orderline.get_product(); const note = orderline.getNote(); const productKey = `${product.id} - ${orderline.get_full_product_name()} - ${note}`; @@ -3098,14 +3103,18 @@ export class Order extends PosModel { ? quantity - oldChanges[lineKey].quantity : quantity; - if (quantityDiff) { + if (quantityDiff && orderline.skipChange === skipped) { changes[productKey] = { name: orderline.get_full_product_name(), product_id: product.id, quantity: quantityDiff, note: note, }; - orderline.setHasChange(true); + changesCount += quantityDiff; + + if (!orderline.skipChange) { + orderline.setHasChange(true); + } } else { orderline.setHasChange(false); } @@ -3132,7 +3141,10 @@ export class Order extends PosModel { } } - return changes; + return { + orderlines: changes, + count: changesCount, + }; } // This function transforms the data generated by getOrderChanges into the old // pattern used by the printer and the display preparation. This old pattern comes from @@ -3141,7 +3153,7 @@ export class Order extends PosModel { const toAdd = []; const toRemove = []; const changes = !cancelled - ? Object.values(this.getOrderChanges()) + ? Object.values(this.getOrderChanges().orderlines) : Object.values(this.lastOrderPrepaChange); for (const lineChange of changes) { @@ -3166,7 +3178,7 @@ export class Order extends PosModel { return this.orderlines.find((orderline) => orderline.skipChange) ? true : false; } hasChangesToPrint() { - return Object.keys(this.getOrderChanges()).length ? true : false; + return this.getOrderChanges().count ? true : false; } async pay() { if (!this.orderlines.length) { @@ -3187,9 +3199,11 @@ export class Order extends PosModel { cancelText: _t("No"), }); if (confirmed) { + this.pos.mobile_pane = "right"; this.pos.env.services.pos.showScreen("PaymentScreen"); } } else { + this.pos.mobile_pane = "right"; this.pos.env.services.pos.showScreen("PaymentScreen"); } } diff --git a/addons/point_of_sale/static/src/scss/pos.scss b/addons/point_of_sale/static/src/scss/pos.scss index 3aab4b96c42..8c8df7764ee 100644 --- a/addons/point_of_sale/static/src/scss/pos.scss +++ b/addons/point_of_sale/static/src/scss/pos.scss @@ -367,71 +367,22 @@ td { width: 25px; border-radius: 50%; } - -.pos .order-button{ +.pos .order-button.floor-button { color: #f0f0f0; - display: inline-block; - box-sizing: border-box; - -moz-box-sizing: border-box; - height: 46px; - padding: 4px 8px; - margin: 3px; - margin-bottom: 0px; - margin-right: 2px; - padding-top: 0px; - background: #8b8b8b; - border-top-left-radius: 3px; - border-top-right-radius: 3px; - vertical-align: top; - line-height: 42px; - text-align: center; - box-shadow: 0px -5px 10px -6px rgb(82,82,82) inset; cursor: pointer; - min-width: 45px; -} - -.pos .order-button.selected{ - font-weight: 900; - background: #EEEEEE; - color: rgb(75,75,75); - height: 45px; - border-bottom: solid 1px rgb(196, 196, 196); - box-shadow: none; - -webkit-flex-shrink: 0; - flex-shrink: 0; -} - -.pos .order-button .order-sequence{ + display: flex; + gap: 7px; + align-items: center; + font-weight: bold; font-size: 16px; - font-weight: 800; - vertical-align: middle; + padding-left: 16px; + padding-right: 16px; + overflow-wrap: anywhere; + text-overflow: hidden; } -.pos .order-button.selected .order-sequence{ - color: white; - background: black; - display: inline-block; - line-height: 24px; - min-width: 24px; - border-radius: 12px; - margin-right: 4px; - margin-left: -4px; -} - -.pos .order-button.square{ - margin-left:1px; - background: #5c5c5c; - color: rgb(160,160,160); - font-size: 18px; - line-height: 45px; -} -.pos .order-button:not(.square) > .fa { - font-size: 16px; - vertical-align: middle; - margin-right: 4px; -} -.pos .order-button .order-sequence{ - font-size: 16px; - font-weight: 800; +.pos .order-button.floor-button .fa{ + font-size: 24px; + margin-bottom: 3px; } .pos .order-selector { @@ -481,35 +432,64 @@ td { /* ********* The leftpane contains the order, numpad and paypad ********* */ +.pos .product-reminder { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + height: 30px; + background: #F7EFCA; + font-size: 12px; + font-weight: bold; + gap: 9px; + + span { + animation: 0.7s anim-lineUp ease-out 1; + margin: 9px 0; + } + + @keyframes anim-lineUp { + 0% { + opacity: 0; + transform: translateY(80%); + } + 20% { + opacity: 0; + } + 50% { + opacity: 1; + transform: translateY(0%); + } + 100% { + opacity: 1; + transform: translateY(0%); + } + } +} + .pos .switchpane { display: flex; } -.pos .switchpane .order-info { - flex-grow: 1; - border-top: 1px solid #ebebeb; - padding: 8px 16px; - background-color: #FFFFFF; - color: $primary; -} -.pos .switchpane .order-info h2 { - padding: 0px; - margin: 3px 0px; - font-weight: bold; -} - .pos .switchpane .btn-switchpane { + height: 70px; + width: 100%; background-color: $primary; border-radius: 0px; color: #FFFFFF; font-size: 15px; font-weight: bold; flex-grow: 1; - flex-basis: 50%; - padding-bottom: 20px; -} -.pos .switchpane .btn-switchpane h1 { - margin-bottom: 0px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + border: none; + + h1 { + font-size: 20px; + margin: 0; + } } .pos .switchpane .btn-switchpane.secondary { @@ -517,6 +497,14 @@ td { color: $primary; } +.pos .switchpane .btn-switchpane.validation-button { + justify-content: center; + font-size: 20px; + font-weight: bold; + border-left: solid 1px white; +} + + .pos .pane-border { border-right: solid 3px $gray-300; } @@ -701,6 +689,13 @@ td { padding: 14px; } +.pos .actionpad .button.mobile-more-button { + padding-left: 40px; + padding-right: 40px; + border: none; + border-bottom: solid 1px #C9CCD2; +} + .pos .actionpad .button.set-partner{ padding-left: 40px; padding-right: 40px; @@ -838,7 +833,11 @@ td { .pos .rightpane-header .categories-header { display: flex; - margin-right: 48px; + overflow-x: auto; + + &::-webkit-scrollbar { + display: none; + } } .pos .rightpane-header .breadcrumbs { @@ -861,6 +860,11 @@ td { padding: 3px; gap: 3px; } +@media only screen and (max-width: 768px) { + .pos .product-list { + grid-template-columns: 1fr 1fr 1fr; + } +} .pos .product-list-container .product-list-empty p { margin: 10px; text-align: center; @@ -1178,6 +1182,10 @@ td { -webkit-line-clamp: 2; } +.pos .product .product-name.no-image { + -webkit-line-clamp: 9; +} + /* ********* The BlockUI Component (frontend) ********* */ .o_blockUI { position: fixed; @@ -1301,7 +1309,7 @@ td { bottom: 0; height: auto; width: 100%; - padding: map-get($spacers, 5) 0; + padding: 48px 0; } .screen .button.next:not(.highlight) { cursor: not-allowed; @@ -1489,20 +1497,24 @@ td { flex-basis: 100%; min-height: 60px; overflow: auto; + align-items: center; } .paymentline:active { background: black; border-color: black; color: white; } -.paymentline .payment-name { +.paymentline .payment-infos { flex-grow: 1; /* make the name of the payment line to be truncated with ellipsis */ white-space: nowrap; text-overflow:ellipsis; padding: map-get($spacers, 3); + display: flex; + align-items: center; + justify-content: space-between; } -.paymentline .payment-amount { +.paymentline .payment-infos .payment-amount { padding: map-get($spacers, 3); } .paymentline .delete-button { @@ -1648,7 +1660,8 @@ td { } .pos .payment-buttons .button { max-width: inherit; - text-align: center; + display: flex; + align-items: center; } .pos .paymentlines-container { min-height: auto; @@ -2566,6 +2579,9 @@ td { padding: 16px; white-space: pre-line; } +.pos .popup .payment-input-number { + text-align: right; +} .pos .popup-med .body { max-height: 400px; @@ -2863,6 +2879,97 @@ td { bottom: 0.2em; } +.pos .modal-dialog .popup.mobile{ + bottom: 0; + position: absolute; + width: 100%; + font-size:20px; + font-weight:bold; + background-color: #fff; + z-index:1200; + font-family: 'Lato'; + font-family: Lato; + margin: 0; + max-height: none; + max-width: none; + transform: none; + top: auto; + left: auto; +} + +.pos .modal-dialog .popup-med.mobile{ + max-width: 700px; + height: calc(100% - 48px); +} + +.pos .popup-med .body.mobile { + overflow-y: auto; + height: calc(100% - 133px); + max-height: none; + margin-bottom: 0; +} + +.pos .popup .footer.mobile { + display: flex; + flex-direction: column; + align-items: center; +} + +.pos .popup .product_configurator_attributes .product-img { + display: flex; + flex-direction: column; + align-items: center; + height: 50%; + padding: 10px; + + & img { + height: 100%; + } + + & .product-info { + display: flex; + width: 100%; + justify-content: space-between; + } +} + +.pos .popup .footer .confirm-line { + display: flex; + width: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 48px; + background: #FFF; + font-size: 12px; + font-weight: bold; + margin: 5px; + + div { + margin: 0; + } +} + +.pos .popup .footer .quantity-selector { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + height: 48px; + background: #FFF; + font-size: 12px; + font-weight: bold; + width: 100%; + border-bottom: 1px solid black; + + button { + margin: 5px; + } +} + +.pos .popup .footer .quantity-selector .quantity{ + margin: 0 10px; +} /* ********* The Webkit Scrollbar ********* */ diff --git a/addons/point_of_sale/static/src/xml/ChromeWidgets/BackButton.xml b/addons/point_of_sale/static/src/xml/ChromeWidgets/BackButton.xml new file mode 100644 index 00000000000..7f4abd1b696 --- /dev/null +++ b/addons/point_of_sale/static/src/xml/ChromeWidgets/BackButton.xml @@ -0,0 +1,14 @@ + + + + + + + BACK + + + + diff --git a/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml b/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml index 6a923540a4d..b51d6f11da8 100644 --- a/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml +++ b/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml @@ -2,7 +2,7 @@ - + diff --git a/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml index a49ca5225b3..6ce367448a7 100644 --- a/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml +++ b/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml @@ -1,39 +1,13 @@ - - - -
+ t-attf-id="{{ attribute.id }}_{{ value.id }}" t-att-value="value.id"/>
@@ -86,4 +60,71 @@
+ + + + + + + + diff --git a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml index 9c5d3891649..949bc5b633a 100644 --- a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml +++ b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml @@ -42,7 +42,8 @@ sendPaymentReverse.bind="sendPaymentReverse" sendPaymentCancel.bind="sendPaymentCancel" sendPaymentRequest.bind="sendPaymentRequest" - selectLine.bind="selectPaymentLine"/> + selectLine.bind="selectPaymentLine" + updateSelectedPaymentline.bind="updateSelectedPaymentline"/>
-
- -
- -
-
+
+
+ +
@@ -110,13 +109,10 @@
- -
diff --git a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreenPaymentLines.xml b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreenPaymentLines.xml index 487acbfc8ab..94191617b60 100644 --- a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreenPaymentLines.xml +++ b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreenPaymentLines.xml @@ -7,19 +7,19 @@
-
- -
-
- - - - - - + t-att-class="selectedLineClass(line)"> +
+ +
+ + + + + + +
+
-
+ t-att-class="unselectedLineClass(line)"> +
+
+ + + + + + +
-
- - - - - - -
+
-
diff --git a/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml b/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml index d61318901eb..e40a7c15636 100644 --- a/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml +++ b/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml @@ -4,14 +4,11 @@
-
+
- -
More...
-
- + @@ -22,25 +19,31 @@ partner="partner" actionName="constructor.numpadActionName" actionType="'payment'" - onSwitchPane.bind="switchPane" /> + onClickMore.bind="displayAllControlPopup" />
-
+
-
- - -
+ +
+ + +
+
+ + +
+
diff --git a/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/ReceiptScreen.xml b/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/ReceiptScreen.xml index 3c24b264326..de5be060010 100644 --- a/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/ReceiptScreen.xml +++ b/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/ReceiptScreen.xml @@ -40,8 +40,8 @@
-
- + +
+
+
+ New Order +
+
+ Resume Order +
+ +
diff --git a/addons/point_of_sale/static/tests/tours/Chrome.tour.js b/addons/point_of_sale/static/tests/tours/Chrome.tour.js index 4cf89092d29..2067ee8501b 100644 --- a/addons/point_of_sale/static/tests/tours/Chrome.tour.js +++ b/addons/point_of_sale/static/tests/tours/Chrome.tour.js @@ -65,7 +65,7 @@ TicketScreen.do.selectOrder("-0001"); ProductScreen.check.isShown(); ProductScreen.do.clickPayButton(); PaymentScreen.do.clickPaymentMethod("Cash"); -PaymentScreen.do.pressNumpad("2 0"); +PaymentScreen.do.enterPaymentLineAmount("Cash", "20"); PaymentScreen.check.remainingIs("0.0"); PaymentScreen.check.validateButtonIsHighlighted(true); PaymentScreen.do.clickValidate(); diff --git a/addons/point_of_sale/static/tests/tours/PaymentScreen.tour.js b/addons/point_of_sale/static/tests/tours/PaymentScreen.tour.js index 25634e9398d..0e51fcb3fc2 100644 --- a/addons/point_of_sale/static/tests/tours/PaymentScreen.tour.js +++ b/addons/point_of_sale/static/tests/tours/PaymentScreen.tour.js @@ -15,15 +15,17 @@ ProductScreen.do.clickPayButton(); PaymentScreen.check.emptyPaymentlines("52.8"); PaymentScreen.do.clickPaymentMethod("Cash"); -PaymentScreen.do.pressNumpad("1 1"); +PaymentScreen.do.enterPaymentLineAmount("Cash", "11"); PaymentScreen.check.selectedPaymentlineHas("Cash", "11.00"); PaymentScreen.check.remainingIs("41.8"); PaymentScreen.check.changeIs("0.0"); PaymentScreen.check.validateButtonIsHighlighted(false); // remove the selected paymentline with multiple backspace presses PaymentScreen.do.pressNumpad("Backspace Backspace"); +PaymentScreen.do.fillPaymentLineAmountMobile("Cash", "0"); PaymentScreen.check.selectedPaymentlineHas("Cash", "0.00"); PaymentScreen.do.pressNumpad("Backspace"); +PaymentScreen.do.clickPaymentlineDelButton("Cash", "0", true); PaymentScreen.check.emptyPaymentlines("52.8"); // Pay with bank, the selected line should have full amount @@ -37,10 +39,12 @@ PaymentScreen.do.clickPaymentlineDelButton("Bank", "52.8"); // Use +10 and +50 to increment the amount of the paymentline PaymentScreen.do.clickPaymentMethod("Cash"); PaymentScreen.do.pressNumpad("+10"); +PaymentScreen.do.fillPaymentLineAmountMobile("Cash", "10"); PaymentScreen.check.remainingIs("42.8"); PaymentScreen.check.changeIs("0.0"); PaymentScreen.check.validateButtonIsHighlighted(false); PaymentScreen.do.pressNumpad("+50"); +PaymentScreen.do.fillPaymentLineAmountMobile("Cash", "60"); PaymentScreen.check.remainingIs("0.0"); PaymentScreen.check.changeIs("7.2"); PaymentScreen.check.validateButtonIsHighlighted(true); @@ -49,20 +53,17 @@ PaymentScreen.do.clickPaymentlineDelButton("Cash", "60.0"); // Multiple paymentlines PaymentScreen.do.clickPaymentMethod("Cash"); PaymentScreen.do.pressNumpad("1"); +PaymentScreen.do.fillPaymentLineAmountMobile("Cash", "1"); PaymentScreen.check.remainingIs("51.8"); PaymentScreen.check.changeIs("0.0"); PaymentScreen.check.validateButtonIsHighlighted(false); -PaymentScreen.do.clickPaymentMethod("Cash"); +PaymentScreen.do.clickPaymentMethod("Bank"); +PaymentScreen.do.fillPaymentLineAmountMobile("Bank", "5"); PaymentScreen.do.pressNumpad("5"); PaymentScreen.check.remainingIs("46.8"); PaymentScreen.check.changeIs("0.0"); PaymentScreen.check.validateButtonIsHighlighted(false); PaymentScreen.do.clickPaymentMethod("Bank"); -PaymentScreen.do.pressNumpad("2 0"); -PaymentScreen.check.remainingIs("26.8"); -PaymentScreen.check.changeIs("0.0"); -PaymentScreen.check.validateButtonIsHighlighted(false); -PaymentScreen.do.clickPaymentMethod("Bank"); PaymentScreen.check.remainingIs("0.0"); PaymentScreen.check.changeIs("0.0"); PaymentScreen.check.validateButtonIsHighlighted(true); @@ -78,7 +79,7 @@ ProductScreen.exec.addOrderline("Letter Tray", "1", "10"); ProductScreen.do.clickPayButton(); PaymentScreen.do.clickPaymentMethod("Bank"); -PaymentScreen.do.pressNumpad("1 0 0 0"); +PaymentScreen.do.enterPaymentLineAmount("Bank", "1000"); PaymentScreen.check.remainingIs("0.0"); PaymentScreen.check.changeIs("0.0"); @@ -196,8 +197,7 @@ ProductScreen.do.clickPayButton(); PaymentScreen.check.totalIs("1.95"); PaymentScreen.do.clickPaymentMethod("Cash"); -PaymentScreen.do.pressNumpad("5"); - +PaymentScreen.do.enterPaymentLineAmount("Cash", "5"); PaymentScreen.check.remainingIs("0.0"); PaymentScreen.check.changeIs("3.05"); PaymentScreen.check.totalDueIs("1.95"); diff --git a/addons/point_of_sale/static/tests/tours/ReceiptScreen.tour.js b/addons/point_of_sale/static/tests/tours/ReceiptScreen.tour.js index 523ba1d7d46..2d400b5d6b4 100644 --- a/addons/point_of_sale/static/tests/tours/ReceiptScreen.tour.js +++ b/addons/point_of_sale/static/tests/tours/ReceiptScreen.tour.js @@ -30,9 +30,10 @@ ProductScreen.exec.addOrderline("Whiteboard Pen", "6", "6", "36.0"); ProductScreen.exec.addOrderline("Monitor Stand", "6", "1", "6.0"); ProductScreen.do.clickPayButton(); PaymentScreen.do.clickPaymentMethod("Cash"); -PaymentScreen.do.pressNumpad("7 0"); +PaymentScreen.do.enterPaymentLineAmount("Cash", "70"); PaymentScreen.check.remainingIs("2.0"); PaymentScreen.do.pressNumpad("0"); +PaymentScreen.do.fillPaymentLineAmountMobile("Cash", "700"); PaymentScreen.check.remainingIs("0.00"); PaymentScreen.check.changeIs("628.0"); PaymentScreen.do.clickValidate(); @@ -48,7 +49,7 @@ ReceiptScreen.do.clickNextOrder(); ProductScreen.exec.addOrderline("Desk Pad", "6", "5"); ProductScreen.do.clickPayButton(); PaymentScreen.do.clickTipButton(); -NumberPopup.do.pressNumpad("1"); +NumberPopup.do.enterValue("1"); NumberPopup.check.inputShownIs("1"); NumberPopup.do.clickConfirm(); PaymentScreen.check.emptyPaymentlines("31.0"); 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 dea24a75eae..8f24324220b 100644 --- a/addons/point_of_sale/static/tests/tours/TicketScreen.tour.js +++ b/addons/point_of_sale/static/tests/tours/TicketScreen.tour.js @@ -125,6 +125,7 @@ TicketScreen.do.pressNumpad("3"); // that can be refunded. ErrorPopup.do.clickConfirm(); TicketScreen.do.clickDiscard(); +ProductScreen.do.goBackToMainScreen(); ProductScreen.check.isShown(); ProductScreen.check.orderIsEmpty(); ProductScreen.do.clickRefund(); @@ -133,6 +134,7 @@ TicketScreen.do.clickOrderline("Desk Pad"); TicketScreen.do.pressNumpad("1"); TicketScreen.check.toRefundTextContains("To Refund: 1.00"); TicketScreen.do.confirmRefund(); +ProductScreen.do.goBackToMainScreen(); ProductScreen.check.isShown(); ProductScreen.check.selectedOrderlineHas("Desk Pad", "-1.00"); // Try changing the refund line to positive number. @@ -151,6 +153,7 @@ ProductScreen.do.clickRefund(); TicketScreen.do.selectOrder("-0005"); TicketScreen.check.toRefundTextContains("Refunding 2.00"); TicketScreen.do.clickDiscard(); +ProductScreen.do.goBackToMainScreen(); // Pay the refund order. ProductScreen.do.clickPayButton(); PaymentScreen.do.clickPaymentMethod("Bank"); 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 4acc5fb22e0..aee1418c973 100644 --- a/addons/point_of_sale/static/tests/tours/acceptance_tour.js +++ b/addons/point_of_sale/static/tests/tours/acceptance_tour.js @@ -20,7 +20,7 @@ function add_product_to_order(product_name) { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".floor-button", mobile: true, }, ]; @@ -35,7 +35,7 @@ function set_fiscal_position_on_order(fp_name) { }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -48,7 +48,7 @@ function set_fiscal_position_on_order(fp_name) { }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -58,7 +58,7 @@ function set_fiscal_position_on_order(fp_name) { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".floor-button", mobile: true, }, ]; @@ -69,10 +69,39 @@ function press_payment_numpad(val) { { content: `press ${val} on payment screen numpad`, trigger: `.payment-numpad .input-button:contains("${val}"):visible`, + mobile: false, }, ]; } +function fillPaymentLineAmountMobile(lineName, keys) { + return [ + { + content: "click payment line", + trigger: `.paymentlines .paymentline .payment-infos:contains("${lineName}")`, + mobile: true, + }, + { + content: `'${keys}' inputed in the number popup`, + trigger: ".popup .payment-input-number", + run: `text ${keys}`, + mobile: true, + }, + { + content: "click confirm button", + trigger: ".popup .footer .confirm", + mobile: true, + } + ]; +} + +function fillPaymentValue(lineName, val) { + return [ + ...press_payment_numpad(val), + ...fillPaymentLineAmountMobile(lineName, val), + ] +} + function press_product_numpad(val) { return [ { @@ -86,7 +115,7 @@ function press_product_numpad(val) { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".floor-button", mobile: true, }, ]; @@ -127,7 +156,7 @@ function selected_orderline_has({ product, price = null, quantity = null }) { } result.push({ content: "go back to the products", - trigger: ".back-button", + trigger: ".floor-button", mobile: true, }); return result; @@ -147,7 +176,7 @@ function verify_order_total(total_str) { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".floor-button", mobile: true, }, ]; @@ -192,6 +221,12 @@ function finish_order() { { content: "click Next Order", trigger: ".receipt-screen .button.next.highlight:visible", + mobile: false, + }, + { + content: "Click Next Order", + trigger: ".receipt-screen .btn-switchpane.validation-button.highlight[name='done']", + mobile: true, }, { content: "check if we left the receipt screen", @@ -229,7 +264,7 @@ steps = steps.concat(goto_payment_screen_and_select_payment_method()); remaining := 0.00 change := 1.50 */ -steps = steps.concat(press_payment_numpad("5")); +steps = steps.concat(fillPaymentValue("Cash", "5")); steps = steps.concat(selected_payment_has("Cash", "5.0")); steps = steps.concat([ { @@ -255,12 +290,12 @@ steps = steps.concat([ steps = steps.concat([ { content: "pay with cash", - trigger: '.paymentmethod:contains("Cash")', + trigger: '.paymentmethod:contains("Bank")', }, ]); -steps = steps.concat(selected_payment_has("Cash", "5.2")); -steps = steps.concat(press_payment_numpad("6")); -steps = steps.concat(selected_payment_has("Cash", "6.0")); +steps = steps.concat(selected_payment_has("Bank", "5.2")); +steps = steps.concat(fillPaymentValue("Bank", "6")); +steps = steps.concat(selected_payment_has("Bank", "6.0")); steps = steps.concat([ { content: "verify remaining", diff --git a/addons/point_of_sale/static/tests/tours/helpers/NumberPopupTourMethods.js b/addons/point_of_sale/static/tests/tours/helpers/NumberPopupTourMethods.js index 3a9724a9ed9..bd6da251b1d 100644 --- a/addons/point_of_sale/static/tests/tours/helpers/NumberPopupTourMethods.js +++ b/addons/point_of_sale/static/tests/tours/helpers/NumberPopupTourMethods.js @@ -27,16 +27,40 @@ class Do { return { content: `'${key}' pressed in numpad`, trigger, + mobile: false, }; } return keys.split(" ").map(generateStep); } + enterValue(keys) { + const numpadKeys = keys.split('').join(' '); + return [ + ...this.pressNumpad(numpadKeys), + ...this.fillPopupValue(keys) + ]; + } + fillPopupValue(keys) { + return [ + { + content: `'${keys}' inputed in the number popup`, + trigger: ".popup .value", + run: `text ${keys}`, + mobile: true, + }, + ]; + } clickConfirm() { return [ { content: "click confirm button", trigger: ".popup-number .footer .confirm", + mobile: false, }, + { + content: "click confirm button", + trigger: ".popup .footer .confirm", + mobile: true, + } ]; } } @@ -46,7 +70,7 @@ class Check { return [ { content: "number popup is shown", - trigger: ".modal-dialog .popup-number", + trigger: ".modal-dialog .popup .value", run: () => {}, }, ]; @@ -55,13 +79,15 @@ class Check { return [ { content: "number input element check", - trigger: ".modal-dialog .popup-number .popup-input", + trigger: ".modal-dialog .popup-number", run: () => {}, + mobile: false, }, { content: `input shown is '${val}'`, - trigger: `.modal-dialog .popup-number .popup-input:contains("${val}")`, + trigger: `.modal-dialog .popup .value:contains("${val}")`, run: () => {}, + mobile: false, }, ]; } diff --git a/addons/point_of_sale/static/tests/tours/helpers/PaymentScreenTourMethods.js b/addons/point_of_sale/static/tests/tours/helpers/PaymentScreenTourMethods.js index 67862f3521b..2575ab03ddf 100644 --- a/addons/point_of_sale/static/tests/tours/helpers/PaymentScreenTourMethods.js +++ b/addons/point_of_sale/static/tests/tours/helpers/PaymentScreenTourMethods.js @@ -17,11 +17,20 @@ class Do { * @param {String} name payment method * @param {String} amount */ - clickPaymentlineDelButton(name, amount) { + clickPaymentlineDelButton(name, amount, mobile=false) { + if (mobile) { + return [ + { + content: `delete ${name} paymentline with ${amount} amount`, + trigger: `.paymentlines .paymentline .payment-infos:contains("${name}") ~ .delete-button`, + mobile: true, + }, + ]; + } return [ { content: `delete ${name} paymentline with ${amount} amount`, - trigger: `.paymentlines .paymentline .payment-name:contains("${name}") ~ .delete-button`, + trigger: `.paymentlines .paymentline .payment-infos:contains("${name}") ~ .delete-button`, }, ]; } @@ -57,7 +66,17 @@ class Do { /** * Press the numpad in sequence based on the given space-separated keys. * Note: Maximum of 2 characters because NumberBuffer only allows 2 consecutive - * fast inputs. Fast inputs is the case in tours. + * fast inputs. Fast inputs is the case in tours. This method is only for the + * desktop environment. The mobile environment doesn't work exactly the same way + * so we have to call fillPaymentLineAmountMobile to have the same behaviour. + * + * e.g. : + * PaymentScreen.do.enterPaymentLineAmount("Cash", "70"); + * PaymentScreen.check.remainingIs("2.0"); + * PaymentScreen.do.pressNumpad("0"); <- desktop: add a 0 + * PaymentScreen.do.fillPaymentLineAmountMobile("Cash", "700"); <- mobile: rewrite the amount + * PaymentScreen.check.remainingIs("0.00"); + * PaymentScreen.check.changeIs("628.0"); * * @param {String} keys space-separated numpad keys */ @@ -76,9 +95,10 @@ class Do { return { content: `'${key}' pressed in payment numpad`, trigger, + mobile: false, }; } - return keys.split(" ").map(generateStep); + return keys.split(' ').map(generateStep); } clickBack() { @@ -97,6 +117,35 @@ class Do { }, ]; } + + enterPaymentLineAmount(lineName, keys) { + const numpadKeys = keys.split('').join(' '); + return [ + ...this.pressNumpad(numpadKeys), + ...this.fillPaymentLineAmountMobile(lineName, keys) + ]; + } + + fillPaymentLineAmountMobile(lineName, keys) { + return [ + { + content: "click payment line", + trigger: `.paymentlines .paymentline .payment-infos:contains("${lineName}")`, + mobile: true, + }, + { + content: `'${keys}' inputed in the number popup`, + trigger: ".popup .payment-input-number", + run: `text ${keys}`, + mobile: true, + }, + { + content: "click confirm button", + trigger: ".popup .footer .confirm", + mobile: true, + } + ]; + } } class Check { 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 cef145ec4a0..cdc967313f1 100644 --- a/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js +++ b/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js @@ -31,7 +31,7 @@ class Do { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -95,7 +95,7 @@ class Do { ...keys.split(" ").map(generateStep), { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -148,7 +148,7 @@ class Do { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -163,7 +163,7 @@ class Do { }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -181,7 +181,7 @@ class Do { }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -201,7 +201,7 @@ class Do { }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { trigger: ".o_pricelist_button" }, @@ -211,7 +211,7 @@ class Do { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -234,7 +234,7 @@ class Do { }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -247,7 +247,7 @@ class Do { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -274,6 +274,15 @@ class Do { }, ]; } + goBackToMainScreen() { + return [ + { + content: "go back to the products", + trigger: ".pos-rightheader .floor-button", + mobile: true, + }, + ] + } } class Check { @@ -321,7 +330,7 @@ class Check { } res.push({ content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }); return res; @@ -340,7 +349,7 @@ class Check { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -369,7 +378,7 @@ class Check { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -388,7 +397,7 @@ class Check { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -442,7 +451,7 @@ class Check { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -461,7 +470,7 @@ class Check { }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }, ]; @@ -537,7 +546,7 @@ class Execute { res.push(...TextAreaPopup._do.clickConfirm()); res.push({ content: "go back to the products", - trigger: ".back-button", + trigger: ".pos-rightheader .floor-button", mobile: true, }); return res; diff --git a/addons/point_of_sale/static/tests/tours/helpers/ReceiptScreenTourMethods.js b/addons/point_of_sale/static/tests/tours/helpers/ReceiptScreenTourMethods.js index 0e4fd2d120f..c74d8e283a5 100644 --- a/addons/point_of_sale/static/tests/tours/helpers/ReceiptScreenTourMethods.js +++ b/addons/point_of_sale/static/tests/tours/helpers/ReceiptScreenTourMethods.js @@ -8,6 +8,12 @@ class Do { { content: "go to next screen", trigger: ".receipt-screen .button.next.highlight[name='done']", + mobile: false, + }, + { + content: "go to next screen", + trigger: ".receipt-screen .btn-switchpane.validation-button.highlight[name='done']", + mobile: true, }, ]; } 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 2496155b26f..e75c98729dd 100644 --- a/addons/point_of_sale/static/tests/tours/pricelist_tour.js +++ b/addons/point_of_sale/static/tests/tours/pricelist_tour.js @@ -112,7 +112,7 @@ steps = steps.concat([ }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -138,7 +138,7 @@ steps = steps.concat([ }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -164,7 +164,7 @@ steps = steps.concat([ }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -182,7 +182,7 @@ steps = steps.concat([ }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".floor-button", mobile: true, }, { @@ -203,7 +203,7 @@ steps = steps.concat([ }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -233,7 +233,7 @@ steps = steps.concat([ }, { content: "go back to the products", - trigger: ".back-button", + trigger: ".floor-button", mobile: true, }, { @@ -284,7 +284,7 @@ steps = steps.concat([ }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { @@ -302,7 +302,7 @@ steps = steps.concat([ }, { content: "click more button", - trigger: ".control-button:contains('More')", + trigger: ".mobile-more-button", mobile: true, }, { diff --git a/addons/pos_hr/static/tests/tours/PosHrTour.js b/addons/pos_hr/static/tests/tours/PosHrTour.js index a681899b2f0..869c94d729a 100644 --- a/addons/pos_hr/static/tests/tours/PosHrTour.js +++ b/addons/pos_hr/static/tests/tours/PosHrTour.js @@ -20,9 +20,10 @@ SelectionPopup.check.hasSelectionItem("Pos Employee2"); SelectionPopup.check.hasSelectionItem("Mitchell Admin"); SelectionPopup.do.clickItem("Pos Employee1"); NumberPopup.check.isShown(); -NumberPopup.do.pressNumpad("2 5"); +NumberPopup.do.enterValue("25"); NumberPopup.check.inputShownIs("••"); NumberPopup.do.pressNumpad("8 1"); +NumberPopup.do.fillPopupValue("2581"); NumberPopup.check.inputShownIs("••••"); NumberPopup.do.clickConfirm(); ErrorPopup.check.isShown(); @@ -30,9 +31,10 @@ ErrorPopup.do.clickConfirm(); PosHr.do.clickLoginButton(); SelectionPopup.do.clickItem("Pos Employee1"); NumberPopup.check.isShown(); -NumberPopup.do.pressNumpad("2 5"); +NumberPopup.do.enterValue("25"); NumberPopup.check.inputShownIs("••"); NumberPopup.do.pressNumpad("8 0"); +NumberPopup.do.fillPopupValue("2580"); NumberPopup.check.inputShownIs("••••"); NumberPopup.do.clickConfirm(); ProductScreen.check.isShown(); @@ -45,9 +47,10 @@ Chrome.do.clickMenuButton(); PosHr.do.clickLockButton(); PosHr.do.clickLoginButton(); SelectionPopup.do.clickItem("Pos Employee2"); -NumberPopup.do.pressNumpad("1 2"); +NumberPopup.do.enterValue("12"); NumberPopup.check.inputShownIs("••"); NumberPopup.do.pressNumpad("3 4"); +NumberPopup.do.fillPopupValue("1234"); NumberPopup.check.inputShownIs("••••"); NumberPopup.do.clickConfirm(); ProductScreen.check.isShown(); diff --git a/addons/pos_loyalty/static/src/js/Loyalty.js b/addons/pos_loyalty/static/src/js/Loyalty.js index 623e939176f..dd02f4075dd 100644 --- a/addons/pos_loyalty/static/src/js/Loyalty.js +++ b/addons/pos_loyalty/static/src/js/Loyalty.js @@ -1682,7 +1682,7 @@ patch(Order.prototype, "pos_loyalty.Order", { return sprintf( _t("Gift Card: %s\nBalance: %s"), code, - this.pos.format_currency(coupon.balance) + this.env.utils.formatCurrency(coupon.balance) ); } return true; diff --git a/addons/pos_restaurant/models/pos_config.py b/addons/pos_restaurant/models/pos_config.py index 64e7e903ba2..c3bbbc17122 100644 --- a/addons/pos_restaurant/models/pos_config.py +++ b/addons/pos_restaurant/models/pos_config.py @@ -2,6 +2,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ +import json +from collections import defaultdict class PosConfig(models.Model): @@ -14,18 +16,36 @@ class PosConfig(models.Model): set_tip_after_payment = fields.Boolean('Set Tip After Payment', help="Adjust the amount authorized by payment terminals to add a tip after the customers left or at the end of the day.") module_pos_restaurant = fields.Boolean(default=True) - def get_tables_order_count(self): - """ """ + def get_tables_order_count_and_printing_changes(self): self.ensure_one() tables = self.env['restaurant.table'].search([('floor_id.pos_config_ids', '=', self.id)]) domain = [('state', '=', 'draft'), ('table_id', 'in', tables.ids)] order_stats = self.env['pos.order']._read_group(domain, ['table_id'], ['__count']) + linked_orderlines = self.env['pos.order.line'].search([('order_id.state', '=', 'draft'), ('order_id.table_id', 'in', tables.ids)]) orders_map = {table.id: count for table, count in order_stats} + changes_map = defaultdict(lambda: 0) + skip_changes_map = defaultdict(lambda: 0) + + for line in linked_orderlines: + last_order_preparation_change = json.loads(line.order_id.last_order_preparation_change) + prep_change = {} + for line_uuid in last_order_preparation_change: + prep_change[last_order_preparation_change[line_uuid]['line_uuid']] = last_order_preparation_change[line_uuid] + quantity_changed = 0 + if line.uuid in prep_change: + quantity_changed = line.qty - prep_change[line.uuid]['quantity'] + else: + quantity_changed = line.qty + + if line.skip_change: + skip_changes_map[line.order_id.table_id.id] += quantity_changed + else: + changes_map[line.order_id.table_id.id] += quantity_changed result = [] for table in tables: - result.append({'id': table.id, 'orders': orders_map.get(table.id, 0)}) + result.append({'id': table.id, 'orders': orders_map.get(table.id, 0), 'changes': changes_map.get(table.id, 0), 'skip_changes': skip_changes_map.get(table.id, 0)}) return result def _get_forbidden_change_fields(self): diff --git a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js index 61d56663672..025ca81deca 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js +++ b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js @@ -14,7 +14,15 @@ import { EditBar } from "./edit_bar"; import { Table } from "./table"; import { usePos } from "@point_of_sale/app/pos_hook"; import { useService } from "@web/core/utils/hooks"; -import { Component, onPatched, onMounted, onWillUnmount, useRef, useState } from "@odoo/owl"; +import { + Component, + onPatched, + onMounted, + onWillUnmount, + useRef, + useState, + onWillStart, +} from "@odoo/owl"; import { sprintf } from "@web/core/utils/strings"; export class FloorScreen extends Component { @@ -37,13 +45,14 @@ export class FloorScreen extends Component { isColorPicker: false, }); const ui = useState(useService("ui")); - this.pos.globalState.floorPlanStyle ||= ui.isSmall ? "kanban" : "default"; + this.pos.globalState.floorPlanStyle = ui.isSmall ? "kanban" : "default"; this.floorMapRef = useRef("floor-map-ref"); this.addFloorRef = useRef("add-floor-ref"); this.map = useRef("map"); onPatched(this.onPatched); onMounted(this.onMounted); onWillUnmount(this.onWillUnmount); + onWillStart(this.onWillStart); } onPatched() { this.floorMapRef.el.style.background = this.state.floorBackground; @@ -55,6 +64,10 @@ export class FloorScreen extends Component { } this.state.floorMapScrollTop = this.floorMapRef.el.getBoundingClientRect().top; } + async onWillStart() { + await this.pos.globalState.unsetTable(); + await this._tableLongpolling(); + } onMounted() { this.pos.openCashControl(); this.floorMapRef.el.style.background = this.state.floorBackground; @@ -66,7 +79,7 @@ export class FloorScreen extends Component { } this.state.floorMapScrollTop = this.floorMapRef.el.getBoundingClientRect().top; // call _tableLongpolling once then set interval of 5sec. - this._tableLongpolling(); + this.tableLongpolling = setInterval(this._tableLongpolling.bind(this), 5000); } onWillUnmount() { @@ -254,9 +267,11 @@ export class FloorScreen extends Component { if (globalState.isEditMode) { return; } - const result = await this.orm.call("pos.config", "get_tables_order_count", [ - globalState.config.id, - ]); + const result = await this.orm.call( + "pos.config", + "get_tables_order_count_and_printing_changes", + [globalState.config.id] + ); for (const table of result) { if (!(table.id in globalState.tables_by_id)) { //We enter this condition if, after loading our PoS, a table is deleted on a PoS and not on another. @@ -278,8 +293,10 @@ export class FloorScreen extends Component { (o.orderlines.length !== 0 || o.paymentlines.length !== 0) && // do not count the orders that are already finalized !o.finalized - ).length; - table_obj.order_count = table.orders + unsynced_orders; + ); + table_obj.order_count = table.orders + unsynced_orders.length; + table_obj.changes_count = table.changes; + table_obj.skip_changes = table.skip_changes; } } get activeFloor() { diff --git a/addons/pos_restaurant/static/src/app/floor_screen/table.js b/addons/pos_restaurant/static/src/app/floor_screen/table.js index 4ea3d3a9644..2b88eb53481 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/table.js +++ b/addons/pos_restaurant/static/src/app/floor_screen/table.js @@ -29,6 +29,20 @@ export class Table extends Component { get style() { const table = this.props.table; + let background = table.color ? table.color : "rgb(53, 211, 116)"; + let textColor = "white"; + let border = "auto"; + let boxShadow = "0px 3px rgba(0,0,0,0.07)"; + if (!this.isOccupied()) { + background = "transparent"; + const rgb = table.floor.background_color.substring(4, table.floor.background_color.length-1) + .replace(/ /g, '') + .split(','); + textColor = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2])/255 > 0.5 ? "black" : "white"; + border = "3px solid " + table.color; + boxShadow = "none"; + } + if (this.pos.globalState.floorPlanStyle == "kanban") { const floor = table.floor; const index = floor.tables.indexOf(table); @@ -47,9 +61,12 @@ export class Table extends Component { line-height: ${widthTable}px; top: ${position_v}px; left: ${position_h}px; + border: ${border}; border-radius: ${table.shape === "round" ? 1000 : 3}px; - background: ${table.color || "rgb(53, 211, 116)"}; + background: ${background}; + box-shadow: ${boxShadow}; font-size: ${widthTable >= 150 ? 32 : 16}px; + color: ${textColor}; `; } else { return ` @@ -58,9 +75,12 @@ export class Table extends Component { line-height: ${table.height}px; top: ${table.position_v}px; left: ${table.position_h}px; + border: ${border}; border-radius: ${table.shape === "round" ? 1000 : 3}px; - background: ${table.color || "rgb(53, 211, 116)"}; + background: ${background}; + box-shadow: ${boxShadow}; font-size: ${table.height >= 150 && table.width >= 150 ? 32 : 16}px; + color: ${textColor}; `; } } @@ -70,11 +90,13 @@ export class Table extends Component { } get orderCount() { const table = this.props.table; - return table.order_count !== undefined - ? table.order_count - : this.pos.globalState - .getTableOrders(table.id) - .filter((o) => o.orderlines.length !== 0 || o.paymentlines.length !== 0).length; + + if (table.changes_count > 0) { + return table.changes_count; + } else if (table.skip_changes > 0) { + return table.skip_changes; + } + return table.order_count; } get orderCountClass() { const countClass = { "order-count": true }; @@ -91,18 +113,14 @@ export class Table extends Component { }`; } _getNotifications() { - const orders = this.pos.globalState.getTableOrders(this.props.table.id); + const table = this.props.table; - let hasChangesCount = 0; - let hasSkippedCount = 0; - for (let i = 0; i < orders.length; i++) { - if (orders[i].hasChangesToPrint()) { - hasChangesCount++; - } else if (orders[i].hasSkippedChanges()) { - hasSkippedCount++; - } - } + const hasChangesCount = table.changes_count; + const hasSkippedCount = table.skip_changes; return hasChangesCount ? { printing: true } : hasSkippedCount ? { skipped: true } : {}; } + isOccupied() { + return this.pos.globalState.getCustomerCount(this.props.table.id) > 0 || this.props.table.order_count > 0; + } } diff --git a/addons/pos_restaurant/static/src/app/navbar/BackButton.js b/addons/pos_restaurant/static/src/app/navbar/BackButton.js new file mode 100644 index 00000000000..fdf0d556914 --- /dev/null +++ b/addons/pos_restaurant/static/src/app/navbar/BackButton.js @@ -0,0 +1,58 @@ +/** @odoo-module */ + +import { BackButton } from "@point_of_sale/app/navbar/BackButton"; +import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; +import { TipScreen } from "@pos_restaurant/js/Screens/TipScreen"; +import { patch } from "@web/core/utils/patch"; + +patch(BackButton.prototype, "pos_restaurant.BackButton", { + get table() { + return this.pos.globalState.table; + }, + get floor() { + return this.table?.floor; + }, + get hasTable() { + return this.table != null; + }, + /** + * @override + * If we have a floor screen, + * the logic of the back button changes a bit. + */ + async backToFloorScreen() { + if (this.pos.mainScreen.component && this.pos.globalState.config.module_pos_restaurant) { + if ( + (this.pos.mainScreen.component === ProductScreen && + this.pos.globalState.mobile_pane == "right") || + this.pos.mainScreen.component === TipScreen + ) { + if (this.table) { + const orders = this.pos.globalState.get_order_list(); + const tableOrders = orders.filter((order) => order.tableId === this.table.id); + const qtyChange = tableOrders.reduce( + (acc, order) => { + const quantityChange = order.getOrderChanges(); + const quantitySkipped = order.getOrderChanges(true); + acc.changed += quantityChange.count; + acc.skipped += quantitySkipped.count; + return acc; + }, + { changed: 0, skipped: 0 } + ); + + this.table.changes_count = qtyChange.changed; + this.table.skip_changes = qtyChange.skipped; + } + + this.pos.showScreen("FloorScreen", { floor: this.floor }); + } else { + this.pos.globalState.mobile_pane = "right"; + this.pos.showScreen("ProductScreen"); + } + } else { + this.pos.globalState.mobile_pane = "right"; + this.pos.showScreen("ProductScreen"); + } + }, +}); diff --git a/addons/pos_restaurant/static/src/app/navbar/BackToFloorButton.js b/addons/pos_restaurant/static/src/app/navbar/BackToFloorButton.js deleted file mode 100644 index 572789f710b..00000000000 --- a/addons/pos_restaurant/static/src/app/navbar/BackToFloorButton.js +++ /dev/null @@ -1,26 +0,0 @@ -/** @odoo-module */ - -import { Component, useState } from "@odoo/owl"; -import { usePos } from "@point_of_sale/app/pos_hook"; -import { useService } from "@web/core/utils/hooks"; - -export class BackToFloorButton extends Component { - static template = "BackToFloorButton"; - - setup() { - this.pos = usePos(); - this.ui = useState(useService("ui")); - } - get table() { - return this.pos.globalState.table; - } - get floor() { - return this.table ? this.table.floor : null; - } - get hasTable() { - return this.table != null; - } - backToFloorScreen() { - this.pos.showScreen("FloorScreen", { floor: this.floor }); - } -} diff --git a/addons/pos_restaurant/static/src/app/navbar/navbar.js b/addons/pos_restaurant/static/src/app/navbar/navbar.js index 08df6c27b74..20faa59c841 100644 --- a/addons/pos_restaurant/static/src/app/navbar/navbar.js +++ b/addons/pos_restaurant/static/src/app/navbar/navbar.js @@ -2,9 +2,7 @@ import { Navbar } from "@point_of_sale/app/navbar/navbar"; import { patch } from "@web/core/utils/patch"; -import { BackToFloorButton } from "./BackToFloorButton"; -patch(Navbar.components, "pos_restaurant.Navbar components", { BackToFloorButton }); patch(Navbar.prototype, "pos_restaurant.Navbar", { /** * If no table is set to pos, which means the current main screen @@ -30,4 +28,7 @@ patch(Navbar.prototype, "pos_restaurant.Navbar", { toggleEditMode() { this.pos.globalState.toggleEditMode(); }, + showBackButton() { + return this._super(...arguments) || (this.pos.showBackButton() && this.pos.globalState.config.module_pos_restaurant); + }, }); diff --git a/addons/pos_restaurant/static/src/app/navbar/navbar.xml b/addons/pos_restaurant/static/src/app/navbar/navbar.xml index e61e4a61a68..3a29521cea7 100644 --- a/addons/pos_restaurant/static/src/app/navbar/navbar.xml +++ b/addons/pos_restaurant/static/src/app/navbar/navbar.xml @@ -10,9 +10,6 @@ Switch Floor View - - - diff --git a/addons/pos_restaurant/static/src/app/pos_store.js b/addons/pos_restaurant/static/src/app/pos_store.js index 119a3cea933..1e6a8c1525b 100644 --- a/addons/pos_restaurant/static/src/app/pos_store.js +++ b/addons/pos_restaurant/static/src/app/pos_store.js @@ -3,7 +3,9 @@ import { patch } from "@web/core/utils/patch"; import { PosStore } from "@point_of_sale/app/pos_store"; import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen"; +import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { FloorScreen } from "@pos_restaurant/app/floor_screen/floor_screen"; +import { TipScreen } from "@pos_restaurant/js/Screens/TipScreen"; const NON_IDLE_EVENTS = [ "mousemove", @@ -65,9 +67,6 @@ patch(PosStore.prototype, "pos_restaurant.PosStore", { ); }, showScreen(screenName) { - if (screenName === "FloorScreen" && this.globalState.table) { - this.globalState.unsetTable(); - } this._super(...arguments); this.setIdleTimer(); }, @@ -95,4 +94,12 @@ patch(PosStore.prototype, "pos_restaurant.PosStore", { } return this._super(...arguments); }, + showBackButton() { + return ( + this._super(...arguments) || + this.mainScreen.component === TipScreen || + (this.mainScreen.component === ProductScreen && + this.globalState.config.module_pos_restaurant) + ); + }, }); diff --git a/addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js b/addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js index abcf22510af..01c667d736a 100644 --- a/addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js @@ -3,10 +3,6 @@ import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen"; import { patch } from "@web/core/utils/patch"; -patch(PaymentScreen, "pos_restaurant.PaymentScreen", { - showBackToFloorButton: true, -}); - patch(PaymentScreen.prototype, "pos_restaurant.PaymentScreen", { setup() { this._super(...arguments); diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ActionpadWidget.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ActionpadWidget.js index b25ddb06e67..5ef40aa34c2 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ActionpadWidget.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ActionpadWidget.js @@ -57,7 +57,7 @@ patch(ActionpadWidget.prototype, "point_of_sale.ActionpadWidget", { }, get categoryCount() { const categories = {}; - const orderChange = this.currentOrder.getOrderChanges(); + const orderChange = this.currentOrder.getOrderChanges().orderlines; for (const idx in orderChange) { const orderline = orderChange[idx]; const category = this.pos.globalState.db.get_product_by_id(orderline.product_id) diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js index 3ab74a31efe..07c739f5996 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js @@ -13,6 +13,9 @@ export class PrintBillButton extends Component { } _isDisabled() { const order = this.pos.globalState.get_order(); + if (!order) { + return false; + } return order.get_orderlines().length === 0; } click() { diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ProductScreen.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ProductScreen.js index 9b95c9738ee..312f472bc1c 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ProductScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ProductScreen.js @@ -3,6 +3,47 @@ import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; import { patch } from "@web/core/utils/patch"; -patch(ProductScreen, "pos_restaurant.ProductScreen", { - showBackToFloorButton: true, +patch(ProductScreen.prototype, "pos_restaurant.ProductScreen", { + /** + * @override + */ + get selectedOrderlineQuantity() { + const order = this.pos.globalState.get_order(); + const orderline = order.get_selected_orderline(); + if ( + this.pos.globalState.config.module_pos_restaurant && + this.pos.globalState.orderPreparationCategories.size + ) { + let orderline_name = orderline.product.display_name; + if (orderline.description) { + orderline_name += " (" + orderline.description + ")"; + } + const changes = Object.values(order.getOrderChanges().orderlines).find( + (change) => change.name == orderline_name + ); + return changes ? changes.quantity : false; + } + return this._super(...arguments); + }, + get selectedOrderlineTotal() { + return this.env.utils.formatCurrency( + this.pos.globalState.get_order().get_selected_orderline().get_display_price() + ); + }, + get swapButton() { + return ( + this.pos.globalState.config.module_pos_restaurant && + this.pos.globalState.orderPreparationCategories.size + ); + }, + submitOrder() { + this.pos.sendOrderInPreparation(this.pos.globalState.get_order()); + }, + primaryPayButton() { + return ( + !this.currentOrder.is_empty() && + ((!this.swapButton && this._super(...arguments)) || + (this.swapButton && this.pos.globalState.get_order().getOrderChanges().count > 0)) + ); + }, }); diff --git a/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js index 7399f461fc6..582eec03229 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js @@ -5,10 +5,6 @@ import { patch } from "@web/core/utils/patch"; import { onWillUnmount } from "@odoo/owl"; import { FloorScreen } from "@pos_restaurant/app/floor_screen/floor_screen"; -patch(ReceiptScreen, "pos_restaurant.ReceiptScreen", { - showBackToFloorButton: true, -}); - patch(ReceiptScreen.prototype, "pos_restaurant.ReceiptScreen", { setup() { this._super(...arguments); diff --git a/addons/pos_restaurant/static/src/js/Screens/TipScreen.js b/addons/pos_restaurant/static/src/js/Screens/TipScreen.js index c07d83d11f3..f6e1eff1b1c 100644 --- a/addons/pos_restaurant/static/src/js/Screens/TipScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/TipScreen.js @@ -11,7 +11,6 @@ import { Component, useRef, onMounted } from "@odoo/owl"; export class TipScreen extends Component { static template = "pos_restaurant.TipScreen"; - static showBackToFloorButton = true; setup() { super.setup(); this.pos = usePos(); diff --git a/addons/pos_restaurant/static/src/js/models.js b/addons/pos_restaurant/static/src/js/models.js index 4ff0bf4a90e..cb0992dadce 100644 --- a/addons/pos_restaurant/static/src/js/models.js +++ b/addons/pos_restaurant/static/src/js/models.js @@ -1,6 +1,7 @@ /** @odoo-module */ import { PosGlobalState, Order, Orderline, Payment } from "@point_of_sale/js/models"; +import { ConnectionLostError } from "@web/core/network/rpc_service"; import { patch } from "@web/core/utils/patch"; patch(PosGlobalState.prototype, "pos_restaurant.PosGlobalState", { @@ -156,8 +157,15 @@ patch(PosGlobalState.prototype, "pos_restaurant.PosGlobalState", { getTableOrders(tableId) { return this.get_order_list().filter((order) => order.tableId === tableId); }, - unsetTable() { - this._syncTableOrdersToServer(); + async unsetTable() { + try { + await this._syncTableOrdersToServer(); + } catch (e) { + if (!(e instanceof ConnectionLostError)) { + throw e; + } + Promise.reject(e); + } this.table = null; this.set_order(null); }, diff --git a/addons/pos_restaurant/static/src/scss/restaurant.scss b/addons/pos_restaurant/static/src/scss/restaurant.scss index 44341ecb082..4c272b4d43b 100644 --- a/addons/pos_restaurant/static/src/scss/restaurant.scss +++ b/addons/pos_restaurant/static/src/scss/restaurant.scss @@ -77,24 +77,6 @@ -webkit-box-orient: vertical; -webkit-line-clamp: 2; } -.pos .order-button.floor-button { - display: flex; - align-items: center; - background: $o-navbar-badge-bg; - font-weight: bold; - font-size: 16px; - padding-left: 16px; - padding-right: 16px; - overflow-wrap: anywhere; - text-overflow: hidden; -} -.pos .order-button.floor-button .table-name { - margin-left: 5px; -} -.pos .order-button.floor-button .fa{ - font-size: 24px; - line-height: 42px; -} /* ------ SPLIT BILL SCREEN ------- */ diff --git a/addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml b/addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml deleted file mode 100644 index 06651b617af..00000000000 --- a/addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - () - - - - - - diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ProductScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ProductScreen.xml new file mode 100644 index 00000000000..837b981b806 --- /dev/null +++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ProductScreen.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js b/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js index bf6a0d6bfe6..2416de9a8d9 100644 --- a/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js +++ b/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js @@ -46,13 +46,13 @@ BillScreen.do.clickOk(); // Test GuestButton ProductScreen.do.clickGuestButton(); -NumberPopup.do.pressNumpad("1 5"); +NumberPopup.do.enterValue("15"); NumberPopup.check.inputShownIs("15"); NumberPopup.do.clickConfirm(); ProductScreen.check.guestNumberIs("15"); ProductScreen.do.clickGuestButton(); -NumberPopup.do.pressNumpad("5"); +NumberPopup.do.enterValue("5"); NumberPopup.check.inputShownIs("5"); NumberPopup.do.clickConfirm(); ProductScreen.check.guestNumberIs("5"); diff --git a/addons/pos_restaurant/static/tests/tours/FloorScreen.tour.js b/addons/pos_restaurant/static/tests/tours/FloorScreen.tour.js index 85f6e423208..6fa646ae471 100644 --- a/addons/pos_restaurant/static/tests/tours/FloorScreen.tour.js +++ b/addons/pos_restaurant/static/tests/tours/FloorScreen.tour.js @@ -88,6 +88,7 @@ FloorScreen.do.clickTable("4"); FloorScreen.check.selectedTableIs("4"); FloorScreen.do.clickSeats(); NumberPopup.do.pressNumpad("Backspace 9"); +NumberPopup.do.fillPopupValue("9"); NumberPopup.check.inputShownIs("9"); NumberPopup.do.clickConfirm(); FloorScreen.check.tableSeatIs("4", "9"); @@ -96,7 +97,7 @@ FloorScreen.check.tableSeatIs("4", "9"); FloorScreen.do.clickTable("4"); FloorScreen.check.selectedTableIs("4"); FloorScreen.do.clickSeats(); -NumberPopup.do.pressNumpad("1 5"); +NumberPopup.do.enterValue("15"); NumberPopup.check.inputShownIs("15"); NumberPopup.do.clickConfirm(); FloorScreen.check.tableSeatIs("4", "15"); @@ -110,13 +111,11 @@ FloorScreen.do.closeEdit(); FloorScreen.check.tableIsNotSelected("4"); FloorScreen.do.clickTable("4"); ProductScreen.check.isShown(); -Chrome.check.backToFloorTextIs("Main Floor", "4"); Chrome.do.backToFloor(); // Opening product screen in second floor should go back to second floor FloorScreen.do.clickFloor("Second Floor"); FloorScreen.check.hasTable("3"); FloorScreen.do.clickTable("3"); -Chrome.check.backToFloorTextIs("Second Floor", "3"); registry.category("web_tour.tours").add("FloorScreenTour", { test: true, url: "/pos/ui", steps: getSteps() }); diff --git a/addons/pos_restaurant/static/tests/tours/TicketScreen.tour.js b/addons/pos_restaurant/static/tests/tours/TicketScreen.tour.js index 804a543671a..b942d5f1bed 100644 --- a/addons/pos_restaurant/static/tests/tours/TicketScreen.tour.js +++ b/addons/pos_restaurant/static/tests/tours/TicketScreen.tour.js @@ -36,7 +36,7 @@ TicketScreen.do.clickNewTicket(); ProductScreen.exec.addOrderline("Coca-Cola", "2", "2"); ProductScreen.check.totalAmountIs("4.0"); Chrome.do.backToFloor(); -FloorScreen.check.orderCountSyncedInTableIs("2", "2"); +FloorScreen.check.orderCountSyncedInTableIs("2", "3"); Chrome.do.clickMenuButton(); Chrome.do.clickTicketButton(); TicketScreen.do.deleteOrder("-0003"); @@ -44,7 +44,6 @@ Chrome.do.confirmPopup(); TicketScreen.do.selectOrder("-0002"); ProductScreen.check.isShown(); ProductScreen.check.totalAmountIs("2.0"); -Chrome.check.backToFloorTextIs("Main Floor", "2"); Chrome.do.backToFloor(); // Make sure that order is deleted properly. @@ -59,7 +58,6 @@ TicketScreen.do.deleteOrder("-0004"); Chrome.do.confirmPopup(); TicketScreen.do.clickDiscard(); FloorScreen.check.isShown(); -FloorScreen.check.orderCountSyncedInTableIs("5", "0"); FloorScreen.do.clickTable("5"); ProductScreen.check.orderIsEmpty(); diff --git a/addons/pos_restaurant/static/tests/tours/TipScreen.tour.js b/addons/pos_restaurant/static/tests/tours/TipScreen.tour.js index 779571c4f7b..5deb4b4e931 100644 --- a/addons/pos_restaurant/static/tests/tours/TipScreen.tour.js +++ b/addons/pos_restaurant/static/tests/tours/TipScreen.tour.js @@ -15,8 +15,8 @@ startSteps(); // Create order that is synced when draft. // order 1 -FloorScreen.do.clickTable("2"); ProductScreen.do.confirmOpeningPopup(); +FloorScreen.do.clickTable("2"); ProductScreen.exec.addOrderline("Minute Maid", "1", "2"); ProductScreen.check.totalAmountIs("2.0"); Chrome.do.backToFloor(); @@ -34,7 +34,7 @@ TicketScreen.do.clickNewTicket(); ProductScreen.exec.addOrderline("Coca-Cola", "2", "2"); ProductScreen.check.totalAmountIs("4.0"); Chrome.do.backToFloor(); -FloorScreen.check.orderCountSyncedInTableIs("2", "1"); +FloorScreen.check.orderCountSyncedInTableIs("2", "2"); Chrome.do.clickMenuButton(); Chrome.do.clickTicketButton(); TicketScreen.check.nthRowContains("2", "Tipping"); @@ -56,7 +56,7 @@ TicketScreen.do.clickNewTicket(); ProductScreen.exec.addOrderline("Coca-Cola", "4", "2"); ProductScreen.check.totalAmountIs("8.0"); Chrome.do.backToFloor(); -FloorScreen.check.orderCountSyncedInTableIs("5", "1"); +FloorScreen.check.orderCountSyncedInTableIs("5", "4"); Chrome.do.clickMenuButton(); Chrome.do.clickTicketButton(); TicketScreen.check.nthRowContains("4", "Tipping"); @@ -122,7 +122,7 @@ ProductScreen.check.isShown(); ProductScreen.do.clickPayButton(); PaymentScreen.do.clickTipButton(); NumberPopup.check.isShown(); -NumberPopup.do.pressNumpad("1"); +NumberPopup.do.enterValue("1"); NumberPopup.check.inputShownIs("1"); NumberPopup.do.clickConfirm(); PaymentScreen.check.emptyPaymentlines("5.0"); @@ -143,4 +143,6 @@ TipScreen.check.isShown(); TipScreen.do.clickSettle(); FloorScreen.check.isShown(); -registry.category("web_tour.tours").add("PosResTipScreenTour", { test: true, url: "/pos/ui", steps: getSteps() }); +registry + .category("web_tour.tours") + .add("PosResTipScreenTour", { test: true, url: "/pos/ui", steps: getSteps() }); diff --git a/addons/pos_restaurant/static/tests/tours/helpers/ChromeTourMethods.js b/addons/pos_restaurant/static/tests/tours/helpers/ChromeTourMethods.js index 3732da171ae..0fddfe38978 100644 --- a/addons/pos_restaurant/static/tests/tours/helpers/ChromeTourMethods.js +++ b/addons/pos_restaurant/static/tests/tours/helpers/ChromeTourMethods.js @@ -14,17 +14,7 @@ class DoExt extends Do { } } -class Check { - backToFloorTextIs(floor, table) { - return [ - { - content: `back to floor text is '${floor} ( ${table} )'`, - trigger: `.floor-button span:contains("${floor}") ~ .table-name:contains("(${table})")`, - run: () => {}, - }, - ]; - } -} +class Check {} class Execute {} diff --git a/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js b/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js index fb253484227..fa06dd57f83 100644 --- a/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js +++ b/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js @@ -12,6 +12,15 @@ class DoExt extends Do { }, ]; } + doubleClickOrderline(name) { + return [ + { + content: "first click orderline", + trigger: `.order .orderline .product-name:contains("${name}")`, + run: "dblclick" + }, + ]; + } clickTransferButton() { return [ { @@ -94,6 +103,15 @@ class CheckExt extends Check { }, ]; } + orderlineIsToSkip(name) { + return [ + { + content: `Line is to order`, + trigger: `.order .orderline.skip-change .product-name:contains("${name}")`, + run: function () {}, // it's a check + }, + ]; + } orderlineHasNote(name, quantity, note) { return [ { diff --git a/addons/pos_restaurant/static/tests/tours/pos_restaurant.js b/addons/pos_restaurant/static/tests/tours/pos_restaurant.js index 211fba1f994..c7c13586fb9 100644 --- a/addons/pos_restaurant/static/tests/tours/pos_restaurant.js +++ b/addons/pos_restaurant/static/tests/tours/pos_restaurant.js @@ -18,10 +18,11 @@ FloorScreen.do.clickTable("5"); ProductScreen.check.orderBtnIsPresent(); ProductScreen.do.clickDisplayedProduct("Coca-Cola"); ProductScreen.check.selectedOrderlineHas("Coca-Cola"); +ProductScreen.do.doubleClickOrderline("Coca-Cola"); ProductScreen.do.clickDisplayedProduct("Water"); ProductScreen.check.selectedOrderlineHas("Water"); ProductScreen.check.orderlineIsToOrder("Water"); -ProductScreen.check.orderlineIsToOrder("Coca-Cola"); +ProductScreen.check.orderlineIsToSkip("Coca-Cola"); ProductScreen.do.clickOrderButton(); ProductScreen.check.orderlinesHaveNoChange(); ProductScreen.check.isPrintingError(); @@ -71,7 +72,7 @@ TicketScreen.do.selectOrder("-0003"); Chrome.do.backToFloor(); // There should be 1 synced draft order. -FloorScreen.check.orderCountSyncedInTableIs("5", "1"); +FloorScreen.check.orderCountSyncedInTableIs("5", "2"); registry.category("web_tour.tours").add("pos_restaurant_sync", { test: true, url: "/pos/ui", steps: getSteps() }); startSteps(); @@ -82,7 +83,6 @@ startSteps(); */ // There is one draft synced order from the previous tour -FloorScreen.check.orderCountSyncedInTableIs("5", "1"); FloorScreen.do.clickTable("5"); ProductScreen.check.totalAmountIs("4.40"); diff --git a/addons/pos_restaurant/tests/test_frontend.py b/addons/pos_restaurant/tests/test_frontend.py index bc63528269d..8b963d8355b 100644 --- a/addons/pos_restaurant/tests/test_frontend.py +++ b/addons/pos_restaurant/tests/test_frontend.py @@ -39,6 +39,13 @@ class TestFrontend(odoo.tests.HttpCase): 'journal_id': second_cash_journal.id, }) + printer = self.env['pos.printer'].create({ + 'name': 'Preparation Printer', + 'epson_printer_ip': '127.0.0.1', + 'printer_type': 'epson_epos', + 'product_categories_ids': [drinks_category.id] + }) + pos_config = self.env['pos.config'].create({ 'name': 'Bar', 'module_pos_restaurant': True, @@ -47,6 +54,7 @@ class TestFrontend(odoo.tests.HttpCase): 'iface_orderline_notes': True, 'iface_start_categ_id': drinks_category.id, 'start_category': True, + 'is_order_printer': True, 'printer_ids': [(4, printer.id)], })