From baf85a88205c80f75569ec81d4deeea466bd9484 Mon Sep 17 00:00:00 2001 From: vlst Date: Tue, 15 Aug 2023 00:21:25 +0200 Subject: [PATCH] [REF] point_of_sale: unify order components At the moment, the pos displays the selected order in multiple screens, such as the `ProductScreen`, the `TicketScreen`, the `SplitBillScreen`, the `self_order` app, but uses completely different implementations for each. This means that when a change needs to be made to the one, it generally needs to be made to the other as well, which is error-prone. In this commit we create generic `OrderWidget` and `Orderline` components, that have not dependencies on POS objects and we refactor the `ProductScreen`, the `TicketScreen` and the `SplitBillScreen` to use these components. The `OrderSummary` component was removed and it's contents are now in the `OrderWidget`. We also introduce a very simple new component called `CenteredIcon` that can be used whenever we want to show an icon that fills a whole div. In this commit we use it for showing the cart icon when the order is empty. In a future commit, the `pos_self_order` will be refactored in order to use the same `OrderWidget` and `Orderline` compoenents as the POS. Task: 3470365 In addition, this commit solves the issue from the task 3491303. closes odoo/odoo#132248 Related: odoo/enterprise#45972 Signed-off-by: Joseph Caburnay (jcb) --- .../static/src/xml/Orderline.xml | 19 -- .../static/src/xml/ProductScreen.xml | 19 ++ .../centered_icon/centered_icon.js | 20 ++ .../src/app/generic_components/input/input.js | 26 +- .../order_widget/order_widget.js | 23 ++ .../order_widget/order_widget.xml | 27 ++ .../generic_components/orderline/orderline.js | 32 +++ .../orderline/orderline.scss | 7 +- .../orderline/orderline.xml | 38 +++ .../product_card/product_card.js | 2 +- .../point_of_sale/static/src/app/pos_app.xml | 4 +- .../app/screens/product_screen/order/order.js | 72 ----- .../screens/product_screen/order/order.xml | 29 --- .../order_summary/order_summary.js | 25 -- .../order_summary/order_summary.xml | 25 -- .../product_screen/orderline/orderline.js | 48 ---- .../product_screen/orderline/orderline.xml | 87 ------- .../screens/product_screen/product_screen.js | 14 +- .../screens/product_screen/product_screen.xml | 29 ++- .../order_details/order_details.js | 25 -- .../order_details/order_details.xml | 58 ----- .../order_details/orderline_details.js | 79 ------ .../order_details/orderline_details.xml | 55 ---- .../screens/ticket_screen/ticket_screen.js | 8 +- .../screens/ticket_screen/ticket_screen.xml | 94 ++++--- .../static/src/app/store/models.js | 116 +++++---- .../static/src/app/store/pos_store.js | 23 ++ .../static/tests/tours/PosComboTour.js | 2 +- .../static/tests/tours/ProductScreen.tour.js | 15 +- .../static/tests/tours/TicketScreen.tour.js | 5 +- .../static/tests/tours/acceptance_tour.js | 28 +- .../tours/helpers/ProductScreenTourMethods.js | 246 ++++-------------- .../tours/helpers/TicketScreenTourMethods.js | 105 ++------ .../generic_components/OrderWidgetMethods.js | 84 ++++++ .../static/tests/tours/helpers/utils.js | 4 + .../static/tests/tours/pricelist_tour.js | 49 +--- .../static/tests/unit/pos_app_tests.js | 17 ++ .../pos_loyalty/static/src/css/Loyalty.scss | 6 +- .../components/order_summary/order_summary.js | 11 - .../order_summary/order_summary.xml | 37 --- .../components/orderline/orderline.js | 31 --- .../components/orderline/orderline.xml | 13 - .../product_screen/product_screen.xml | 31 +++ .../static/src/overrides/models/orderline.js | 35 +++ .../tests/tours/GiftCardProgramTours.js | 164 ++++++------ .../tests/tours/PosLoyaltyTourMethods.js | 54 ++-- .../src/app/product_screen/order/order.js | 44 ---- .../src/app/product_screen/order/order.xml | 8 - .../src/app/product_screen/product_screen.js | 25 ++ .../src/app/product_screen/product_screen.xml | 8 + .../split_bill_screen/split_bill_screen.js | 12 +- .../split_bill_screen/split_bill_screen.xml | 21 +- .../split_orderline/split_orderline.js | 15 -- .../split_orderline/split_orderline.xml | 48 ---- .../product_screen/orderline/orderline.xml | 15 -- .../product_screen/product_screen.xml | 5 + .../static/src/overrides/models/models.js | 12 + .../static/tests/tours/ControlButtons.tour.js | 113 ++++---- .../static/tests/tours/Refund.tour.js | 4 +- .../tests/tours/SplitBillScreen.tour.js | 7 +- .../tours/helpers/ProductScreenTourMethods.js | 53 +--- .../helpers/SplitBillScreenTourMethods.js | 30 +-- .../static/tests/tours/pos_restaurant.js | 2 +- .../product_screen/orderline/orderline.xml | 27 -- .../product_screen/product_screen.xml | 24 ++ .../static/tests/tours/PosSaleTour.js | 3 +- 66 files changed, 913 insertions(+), 1504 deletions(-) delete mode 100644 addons/l10n_fr_pos_cert/static/src/xml/Orderline.xml create mode 100644 addons/l10n_fr_pos_cert/static/src/xml/ProductScreen.xml create mode 100644 addons/point_of_sale/static/src/app/generic_components/centered_icon/centered_icon.js create mode 100644 addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.js create mode 100644 addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.xml create mode 100644 addons/point_of_sale/static/src/app/generic_components/orderline/orderline.js rename addons/point_of_sale/static/src/app/{screens/product_screen => generic_components}/orderline/orderline.scss (60%) create mode 100644 addons/point_of_sale/static/src/app/generic_components/orderline/orderline.xml delete mode 100644 addons/point_of_sale/static/src/app/screens/product_screen/order/order.js delete mode 100644 addons/point_of_sale/static/src/app/screens/product_screen/order/order.xml delete mode 100644 addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.js delete mode 100644 addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.xml delete mode 100644 addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.js delete mode 100644 addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.xml delete mode 100644 addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.js delete mode 100644 addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/order_details.xml delete mode 100644 addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.js delete mode 100644 addons/point_of_sale/static/src/app/screens/ticket_screen/order_details/orderline_details.xml create mode 100644 addons/point_of_sale/static/tests/tours/helpers/generic_components/OrderWidgetMethods.js delete mode 100644 addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.js delete mode 100644 addons/pos_loyalty/static/src/overrides/components/order_summary/order_summary.xml delete mode 100644 addons/pos_loyalty/static/src/overrides/components/orderline/orderline.js delete mode 100644 addons/pos_loyalty/static/src/overrides/components/orderline/orderline.xml create mode 100644 addons/pos_loyalty/static/src/overrides/components/product_screen/product_screen.xml create mode 100644 addons/pos_loyalty/static/src/overrides/models/orderline.js delete mode 100644 addons/pos_restaurant/static/src/app/product_screen/order/order.js delete mode 100644 addons/pos_restaurant/static/src/app/product_screen/order/order.xml create mode 100644 addons/pos_restaurant/static/src/app/product_screen/product_screen.js create mode 100644 addons/pos_restaurant/static/src/app/product_screen/product_screen.xml delete mode 100644 addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.js delete mode 100644 addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.xml delete mode 100644 addons/pos_restaurant/static/src/overrides/components/product_screen/orderline/orderline.xml delete mode 100644 addons/pos_sale/static/src/overrides/components/product_screen/orderline/orderline.xml create mode 100644 addons/pos_sale/static/src/overrides/components/product_screen/product_screen.xml diff --git a/addons/l10n_fr_pos_cert/static/src/xml/Orderline.xml b/addons/l10n_fr_pos_cert/static/src/xml/Orderline.xml deleted file mode 100644 index 4508684c59a..00000000000 --- a/addons/l10n_fr_pos_cert/static/src/xml/Orderline.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - -
  • - Old unit price: - - - / Units - - -
  • -
    -
    -
    - -
    diff --git a/addons/l10n_fr_pos_cert/static/src/xml/ProductScreen.xml b/addons/l10n_fr_pos_cert/static/src/xml/ProductScreen.xml new file mode 100644 index 00000000000..088f238323e --- /dev/null +++ b/addons/l10n_fr_pos_cert/static/src/xml/ProductScreen.xml @@ -0,0 +1,19 @@ + + + + + + +
  • + Old unit price: + + + / Units + + +
  • +
    +
    +
    + +
    diff --git a/addons/point_of_sale/static/src/app/generic_components/centered_icon/centered_icon.js b/addons/point_of_sale/static/src/app/generic_components/centered_icon/centered_icon.js new file mode 100644 index 00000000000..1cc21cc2bdf --- /dev/null +++ b/addons/point_of_sale/static/src/app/generic_components/centered_icon/centered_icon.js @@ -0,0 +1,20 @@ +/** @odoo-module */ + +import { Component, xml } from "@odoo/owl"; + +export class CenteredIcon extends Component { + static props = { + icon: String, + text: { type: String, optional: true }, + class: { type: String, optional: true }, + }; + static defaultProps = { + class: "", + }; + static template = xml` +
    + +

    +

    + `; +} diff --git a/addons/point_of_sale/static/src/app/generic_components/input/input.js b/addons/point_of_sale/static/src/app/generic_components/input/input.js index 4179a9e5f4f..7c6af680498 100644 --- a/addons/point_of_sale/static/src/app/generic_components/input/input.js +++ b/addons/point_of_sale/static/src/app/generic_components/input/input.js @@ -5,19 +5,19 @@ import { useAutofocus } from "@web/core/utils/hooks"; import { debounce } from "@web/core/utils/timing"; /** - This component is meant to provide a "batteries included" api for working - with inputs. It is well suited to work as a search bar or as a monetary input. - Optional props allow handling debouncing, toggling between mobile and desktop views, - autofocus, validation, and more. - The only required prop is tModel, which is an array that represents the - state variable that this component should read and write to. The first - element of the array is the object returned by `useState` and second is - either a `string` or an `int` representing the key to access the value. - ex: normally you would write - `````` - with this component you can write - `````` -*/ + * This component is meant to provide a "batteries included" api for working + * with inputs. It is well suited to work as a search bar or as a monetary input. + * Optional props allow handling debouncing, toggling between mobile and desktop views, + * autofocus, validation, and more. + * The only required prop is tModel, which is an array that represents the + * state variable that this component should read and write to. The first + * element of the array is the object returned by `useState` and second is + * either a `string` or an `int` representing the key to access the value. + * ex: normally you would write + * `````` + * with this component you can write + * `````` + */ export class Input extends Component { static template = "point_of_sale.input"; static props = { diff --git a/addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.js b/addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.js new file mode 100644 index 00000000000..7b071245726 --- /dev/null +++ b/addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.js @@ -0,0 +1,23 @@ +/** @odoo-module */ + +import { Component, useEffect, useRef } from "@odoo/owl"; +import { CenteredIcon } from "@point_of_sale/app/generic_components/centered_icon/centered_icon"; + +export class OrderWidget extends Component { + static template = "point_of_sale.OrderWidget"; + static props = { + lines: { type: Array, element: Object }, + slots: { type: Object }, + total: { type: String, optional: true }, + tax: { type: String, optional: true }, + }; + static components = { CenteredIcon }; + setup() { + this.scrollableRef = useRef("scrollable"); + useEffect(() => { + this.scrollableRef.el + ?.querySelector(".orderline.selected") + ?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + } +} diff --git a/addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.xml b/addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.xml new file mode 100644 index 00000000000..bd669bf7787 --- /dev/null +++ b/addons/point_of_sale/static/src/app/generic_components/order_widget/order_widget.xml @@ -0,0 +1,27 @@ + + + + +
    + + + + + + +
    +
    + Total: + +
    + Taxes: +
    +
    + + + + + + + +
    diff --git a/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.js b/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.js new file mode 100644 index 00000000000..5376c340fb7 --- /dev/null +++ b/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.js @@ -0,0 +1,32 @@ +/** @odoo-module */ + +import { Component } from "@odoo/owl"; + +/** + * How to extend this component: + */ +export class Orderline extends Component { + static template = "point_of_sale.Orderline"; + static props = { + class: { type: Object, optional: true }, + line: { + type: Object, + shape: { + productName: String, + price: String, + qty: String, + unit: { type: String, optional: true }, + unitPrice: String, + discount: { type: String, optional: true }, + comboParent: { type: String, optional: true }, + oldUnitPrice: { type: String, optional: true }, + customerNote: { type: String, optional: true }, + internalNote: { type: String, optional: true }, + }, + }, + slots: { type: Object, optional: true }, + }; + static defaultProps = { + class: {}, + }; +} diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.scss b/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.scss similarity index 60% rename from addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.scss rename to addons/point_of_sale/static/src/app/generic_components/orderline/orderline.scss index a6d34b96b49..5d25e7cfc55 100644 --- a/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.scss +++ b/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.scss @@ -1,5 +1,5 @@ .orderline { - + &:hover { background-color: $o-gray-200; } @@ -7,9 +7,4 @@ &.selected { background-color: $o-component-active-bg; } - - &.skip-change { - color: #7F82AC; - border-color: #7F82AC !important; - } } diff --git a/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.xml b/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.xml new file mode 100644 index 00000000000..9d98b6d0dbe --- /dev/null +++ b/addons/point_of_sale/static/src/app/generic_components/orderline/orderline.xml @@ -0,0 +1,38 @@ + + + + +
  • +
    +
    + + +
    +
    +
    +
      +
    • + + x + / +
    • +
    • + With a % discount +
    • +
    • + + +
    • + +
    • + + +
    • +
    • + + +
    • +
    +
  • +
    +
    diff --git a/addons/point_of_sale/static/src/app/generic_components/product_card/product_card.js b/addons/point_of_sale/static/src/app/generic_components/product_card/product_card.js index f31e728c3ed..154d6da1d51 100644 --- a/addons/point_of_sale/static/src/app/generic_components/product_card/product_card.js +++ b/addons/point_of_sale/static/src/app/generic_components/product_card/product_card.js @@ -5,7 +5,7 @@ import { Component } from "@odoo/owl"; export class ProductCard extends Component { static template = "point_of_sale.ProductCard"; static props = { - class: String, + class: { String, optional: true }, name: String, productId: Number, price: String, diff --git a/addons/point_of_sale/static/src/app/pos_app.xml b/addons/point_of_sale/static/src/app/pos_app.xml index c30dd3dffe8..d903d9fca44 100644 --- a/addons/point_of_sale/static/src/app/pos_app.xml +++ b/addons/point_of_sale/static/src/app/pos_app.xml @@ -5,13 +5,13 @@
    -
    +
    + t-props="pos.tempScreen.props" t-key="pos.tempScreen.name" />
    diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/order/order.js b/addons/point_of_sale/static/src/app/screens/product_screen/order/order.js deleted file mode 100644 index 29cbb409ed3..00000000000 --- a/addons/point_of_sale/static/src/app/screens/product_screen/order/order.js +++ /dev/null @@ -1,72 +0,0 @@ -/** @odoo-module **/ - -import { _t } from "@web/core/l10n/translation"; -import { useService } from "@web/core/utils/hooks"; -import { EditListPopup } from "@point_of_sale/app/store/select_lot_popup/select_lot_popup"; - -import { Orderline } from "@point_of_sale/app/screens/product_screen/orderline/orderline"; -import { OrderSummary } from "@point_of_sale/app/screens/product_screen/order_summary/order_summary"; -import { Component, useEffect, useRef } from "@odoo/owl"; -import { usePos } from "@point_of_sale/app/store/pos_hook"; - -export class OrderWidget extends Component { - static components = { Orderline, OrderSummary }; - static template = "point_of_sale.OrderWidget"; - - setup() { - this.pos = usePos(); - this.popup = useService("popup"); - this.numberBuffer = useService("number_buffer"); - this.scrollableRef = useRef("scrollable"); - useEffect( - () => { - const selectedLineEl = - this.scrollableRef.el && - this.scrollableRef.el.querySelector(".orderline.selected"); - if (selectedLineEl) { - selectedLineEl.scrollIntoView({ behavior: "smooth", block: "start" }); - } - }, - () => [this.order.selected_orderline] - ); - } - get order() { - return this.pos.get_order(); - } - get orderlinesArray() { - return this.order ? this.order.get_orderlines() : []; - } - selectLine(orderline) { - this.numberBuffer.reset(); - this.order.select_orderline(orderline); - } - // IMPROVEMENT: Might be better to lift this to ProductScreen - // because there is similar operation when clicking a product. - // - // Furthermore, what if a number different from 1 (or -1) is specified - // to an orderline that has product tracked by lot. Lot tracking (based - // on the current implementation) requires that 1 item per orderline is - // allowed. - async editPackLotLines(orderline) { - const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot(); - const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot); - const { confirmed, payload } = await this.popup.add(EditListPopup, { - title: _t("Lot/Serial Number(s) Required"), - name: orderline.product.display_name, - isSingleItem: isAllowOnlyOneLot, - array: packLotLinesToEdit, - }); - if (confirmed) { - // Segregate the old and new packlot lines - const modifiedPackLotLines = Object.fromEntries( - payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text]) - ); - const newPackLotLines = payload.newArray - .filter((item) => !item.id) - .map((item) => ({ lot_name: item.text })); - - orderline.setPackLotLines({ modifiedPackLotLines, newPackLotLines }); - } - this.order.select_orderline(orderline); - } -} diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/order/order.xml b/addons/point_of_sale/static/src/app/screens/product_screen/order/order.xml deleted file mode 100644 index d0ff8167e1d..00000000000 --- a/addons/point_of_sale/static/src/app/screens/product_screen/order/order.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - -
    -
    - -
    - -

    This order is empty

    -
    -
    - -
      - - - -
    - -
    -
    -
    -
    - -
    diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.js b/addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.js deleted file mode 100644 index a87b1588fd0..00000000000 --- a/addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.js +++ /dev/null @@ -1,25 +0,0 @@ -/** @odoo-module */ - -import { Component } from "@odoo/owl"; -import { usePos } from "@point_of_sale/app/store/pos_hook"; -import { floatIsZero } from "@web/core/utils/numbers"; - -export class OrderSummary extends Component { - static template = "point_of_sale.OrderSummary"; - - setup() { - this.pos = usePos(); - } - getTotal() { - return this.env.utils.formatCurrency(this.props.order.get_total_with_tax()); - } - getTax() { - const total = this.props.order.get_total_with_tax(); - const totalWithoutTax = this.props.order.get_total_without_tax(); - const taxAmount = total - totalWithoutTax; - return { - hasTax: !floatIsZero(taxAmount, this.pos.currency.decimal_places), - displayAmount: this.env.utils.formatCurrency(taxAmount), - }; - } -} diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.xml b/addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.xml deleted file mode 100644 index 7b76f9fa70e..00000000000 --- a/addons/point_of_sale/static/src/app/screens/product_screen/order_summary/order_summary.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - -
    - - -
    -
    - Total: - - - -
    - Taxes: - - - -
    -
    -
    -
    -
    - -
    diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.js b/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.js deleted file mode 100644 index 08b413b9860..00000000000 --- a/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.js +++ /dev/null @@ -1,48 +0,0 @@ -/** @odoo-module */ - -import { Component } from "@odoo/owl"; -import { usePos } from "@point_of_sale/app/store/pos_hook"; - -export class Orderline extends Component { - static template = "point_of_sale.Orderline"; - - /** - * if doubleclick, change hasChange to skipChange - * - * IMPROVEMENT: Instead of handling both double click and click in single - * method, perhaps we can separate double click from single click. - */ - setup() { - this.pos = usePos(); - } - selectLine() { - const line = this.props.line; // the orderline - if (this.pos.get_order().selected_orderline.id !== line.id) { - this.mp_dbclk_time = new Date().getTime(); - } else if (!this.mp_dbclk_time) { - this.mp_dbclk_time = new Date().getTime(); - } else if (this.mp_dbclk_time + 500 > new Date().getTime()) { - line.toggleSkipChange(); - this.mp_dbclk_time = 0; - } else { - this.mp_dbclk_time = new Date().getTime(); - } - - this.props.selectLine(this.props.line); - } - lotIconClicked() { - this.props.editPackLotLines(this.props.line); - } - get addedClasses() { - return { - selected: this.props.line.selected, - "has-change ps-2 text-success border-start border-success border-4": - this.props.line.hasChange && this.pos.config.module_pos_restaurant, - "skip-change ps-2 border-start border-secondary border-4": - this.props.line.skipChange && this.pos.config.module_pos_restaurant, - }; - } - get customerNote() { - return this.props.line.get_customer_note(); - } -} diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.xml b/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.xml deleted file mode 100644 index 82109cf3081..00000000000 --- a/addons/point_of_sale/static/src/app/screens/product_screen/orderline/orderline.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - -
  • - - - - - - - - - - - - - - -
      - -
    • - - - - - at - - - - - - - - - - / - -
    • -
      - -
    • - With a - - % - - discount -
    • -
      - -
    • - - -
    • -
      - -
    - -
      - -
    • - SN -
    • -
      -
    -
    -
  • -
    - - -
  • - - -
  • -
    -
    diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js index 14807e120d4..fec1d3ec129 100644 --- a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js +++ b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.js @@ -17,17 +17,18 @@ import { Component, onMounted, useState } from "@odoo/owl"; import { ErrorBarcodePopup } from "@point_of_sale/app/barcode/error_popup/barcode_error_popup"; import { Numpad } from "@point_of_sale/app/generic_components/numpad/numpad"; -import { OrderWidget } from "@point_of_sale/app/screens/product_screen/order/order"; import { ProductsWidget } from "@point_of_sale/app/screens/product_screen/product_list/product_list"; import { ActionpadWidget } from "@point_of_sale/app/screens/product_screen/action_pad/action_pad"; - +import { Orderline } from "@point_of_sale/app/generic_components/orderline/orderline"; +import { OrderWidget } from "@point_of_sale/app/generic_components/order_widget/order_widget"; export class ProductScreen extends ControlButtonsMixin(Component) { static template = "point_of_sale.ProductScreen"; static components = { ActionpadWidget, Numpad, - OrderWidget, ProductsWidget, + Orderline, + OrderWidget, }; static numpadActionName = _t("Payment"); @@ -51,7 +52,7 @@ export class ProductScreen extends ControlButtonsMixin(Component) { gs1: this._barcodeGS1Action, }); - // Call `resset` when the `onMounted` callback in `numberBuffer.use` is done. + // Call `reset` when the `onMounted` callback in `numberBuffer.use` is done. // We don't do this in the `mounted` lifecycle method because it is called before // the callbacks in `onMounted` hook. onMounted(() => this.numberBuffer.reset()); @@ -97,6 +98,11 @@ export class ProductScreen extends ControlButtonsMixin(Component) { this.numberBuffer.sendKey(buttonValue); } + + selectLine(orderline) { + this.numberBuffer.reset(); + this.currentOrder.select_orderline(orderline); + } /** * To be overridden by modules that checks availability of * connected scale. diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml index 828113c17e7..01e7cb0dec2 100644 --- a/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml +++ b/addons/point_of_sale/static/src/app/screens/product_screen/product_screen.xml @@ -1,12 +1,31 @@ -
    -
    - +
    + + + + + + +
      +
    • + SN +
    • +
    +
    + +
    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 @@ - - - - -
    -
    -
    - -
    - -

    Select an order

    -
    -
    - -
    - -

    Order is empty

    -
    -
    - -
    - Select the product(s) to refund and set the quantity -
    -
      - - - -
    - -
    -
    -
    - Total: - - - -
    - Taxes: - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    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: + +
  • +
    +
    -
    - - -
    -
    - - + +
    + + +
    +
    + + +
    +
    +
    +
    - - -
    - -
    -
    +
    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() - - -
      - Current Balance: -
    -
    -
    -
    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 @@ - - - - - - - - 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 @@ + + + + + + + + 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

    +
    -
    -
    -
      - - - -
    -
    +
    + + + +
    -
    +
    diff --git a/addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.js b/addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.js deleted file mode 100644 index 43ee4696fcf..00000000000 --- a/addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @odoo-module */ - -import { Component } from "@odoo/owl"; - -export class SplitOrderline extends Component { - static template = "pos_restaurant.SplitOrderline"; - - setup() {} - get isSelected() { - return this.props.split.quantity !== 0; - } - click() { - this.props.onClickLine(this.props.line); - } -} diff --git a/addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.xml b/addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.xml deleted file mode 100644 index 253a42cda0e..00000000000 --- a/addons/pos_restaurant/static/src/app/split_bill_screen/split_orderline/split_orderline.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - -
  • - - - - - - -
      - -
    • - - - - - / - - - - - - - - - at - - / - -
    • -
      - -
    • - With a - - - % - - discount -
    • -
      -
    -
  • -
    - -
    diff --git a/addons/pos_restaurant/static/src/overrides/components/product_screen/orderline/orderline.xml b/addons/pos_restaurant/static/src/overrides/components/product_screen/orderline/orderline.xml deleted file mode 100644 index a393eafa89e..00000000000 --- a/addons/pos_restaurant/static/src/overrides/components/product_screen/orderline/orderline.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - -
  • - - -
  • -
    -
    -
    - -
    diff --git a/addons/pos_restaurant/static/src/overrides/components/product_screen/product_screen.xml b/addons/pos_restaurant/static/src/overrides/components/product_screen/product_screen.xml index 375645e2736..9747794462f 100644 --- a/addons/pos_restaurant/static/src/overrides/components/product_screen/product_screen.xml +++ b/addons/pos_restaurant/static/src/overrides/components/product_screen/product_screen.xml @@ -22,5 +22,10 @@
    + + + () => line.toggleSkipChange() + + diff --git a/addons/pos_restaurant/static/src/overrides/models/models.js b/addons/pos_restaurant/static/src/overrides/models/models.js index 5a2c5123785..aa5f00c37dd 100644 --- a/addons/pos_restaurant/static/src/overrides/models/models.js +++ b/addons/pos_restaurant/static/src/overrides/models/models.js @@ -92,6 +92,18 @@ patch(Orderline.prototype, { return "" + this.id; } }, + toggleSkipChange() { + if (this.hasChange || this.skipChange) { + this.skipChange = !this.skipChange; + } + }, + getDisplayClasses() { + return { + ...super.getDisplayClasses(), + "has-change text-success border-start border-success border-4": this.hasChange, + "skip-change text-primary border-start border-primary border-4": this.skipChange, + }; + }, }); patch(Payment.prototype, { diff --git a/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js b/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js index ea2d09050f5..065f378e50b 100644 --- a/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js +++ b/addons/pos_restaurant/static/tests/tours/ControlButtons.tour.js @@ -7,62 +7,65 @@ import { FloorScreen } from "@pos_restaurant/../tests/tours/helpers/FloorScreenT import { ProductScreen } from "@pos_restaurant/../tests/tours/helpers/ProductScreenTourMethods"; import { SplitBillScreen } from "@pos_restaurant/../tests/tours/helpers/SplitBillScreenTourMethods"; import { BillScreen } from "@pos_restaurant/../tests/tours/helpers/BillScreenTourMethods"; +import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods"; import { getSteps, startSteps } from "@point_of_sale/../tests/tours/helpers/utils"; import { registry } from "@web/core/registry"; -registry - .category("web_tour.tours") - .add("ControlButtonsTour", { - test: true, - url: "/pos/ui", - steps: () => { - // signal to start generating steps - // when finished, steps can be taken from getSteps - startSteps(); - - // Test TransferOrderButton - FloorScreen.do.clickTable("2"); - ProductScreen.exec.addOrderline("Water", "5", "2", "10.0"); - ProductScreen.do.clickTransferButton(); - FloorScreen.do.clickTable("4"); - ProductScreen.do.clickOrderline("Water", "5", "2"); - Chrome.do.backToFloor(); - FloorScreen.do.clickTable("2"); - ProductScreen.check.orderIsEmpty(); - Chrome.do.backToFloor(); - FloorScreen.do.clickTable("4"); - ProductScreen.do.clickOrderline("Water", "5", "2"); - - // Test SplitBillButton - ProductScreen.do.clickSplitBillButton(); - SplitBillScreen.do.clickBack(); - - // Test OrderlineNoteButton - ProductScreen.do.clickNoteButton(); - TextAreaPopup.check.isShown(); - TextAreaPopup.do.inputText("test note"); - TextAreaPopup.do.clickConfirm(); - ProductScreen.check.orderlineHasNote("Water", "5", "test note"); - ProductScreen.exec.addOrderline("Water", "8", "1", "8.0"); - - // Test PrintBillButton - ProductScreen.do.clickPrintBillButton(); - BillScreen.check.isShown(); - BillScreen.do.clickOk(); - - // Test GuestButton - ProductScreen.do.clickGuestButton(); - NumberPopup.do.enterValue("15"); - NumberPopup.check.inputShownIs("15"); - NumberPopup.do.clickConfirm(); - ProductScreen.check.guestNumberIs("15"); - - ProductScreen.do.clickGuestButton(); - NumberPopup.do.enterValue("5"); - NumberPopup.check.inputShownIs("5"); - NumberPopup.do.clickConfirm(); - ProductScreen.check.guestNumberIs("5"); +registry.category("web_tour.tours").add("ControlButtonsTour", { + test: true, + url: "/pos/ui", + steps: () => { + // signal to start generating steps + // when finished, steps can be taken from getSteps + startSteps(); - return getSteps(); - } - }); + // Test TransferOrderButton + FloorScreen.do.clickTable("2"); + ProductScreen.exec.addOrderline("Water", "5", "2", "10.0"); + ProductScreen.do.clickTransferButton(); + FloorScreen.do.clickTable("4"); + Chrome.do.backToFloor(); + FloorScreen.do.clickTable("2"); + ProductScreen.check.orderIsEmpty(); + Chrome.do.backToFloor(); + FloorScreen.do.clickTable("4"); + + // Test SplitBillButton + ProductScreen.do.clickSplitBillButton(); + SplitBillScreen.do.clickBack(); + + // Test OrderlineNoteButton + ProductScreen.do.clickNoteButton(); + TextAreaPopup.check.isShown(); + TextAreaPopup.do.inputText("test note"); + TextAreaPopup.do.clickConfirm(); + Order.hasLine({ + productName: "Water", + quantity: "5", + price: "10.0", + customerNote: "test note", + withClass: ".selected", + }); + ProductScreen.exec.addOrderline("Water", "8", "1", "8.0"); + + // Test PrintBillButton + ProductScreen.do.clickPrintBillButton(); + BillScreen.check.isShown(); + BillScreen.do.clickOk(); + + // Test GuestButton + ProductScreen.do.clickGuestButton(); + NumberPopup.do.enterValue("15"); + NumberPopup.check.inputShownIs("15"); + NumberPopup.do.clickConfirm(); + ProductScreen.check.guestNumberIs("15"); + + ProductScreen.do.clickGuestButton(); + NumberPopup.do.enterValue("5"); + NumberPopup.check.inputShownIs("5"); + NumberPopup.do.clickConfirm(); + ProductScreen.check.guestNumberIs("5"); + + return getSteps(); + }, +}); diff --git a/addons/pos_restaurant/static/tests/tours/Refund.tour.js b/addons/pos_restaurant/static/tests/tours/Refund.tour.js index 88e34cac48b..6064988c404 100644 --- a/addons/pos_restaurant/static/tests/tours/Refund.tour.js +++ b/addons/pos_restaurant/static/tests/tours/Refund.tour.js @@ -34,7 +34,9 @@ registry.category("web_tour.tours").add("RefundStayCurrentTableTour", { ProductScreen.check.orderIsEmpty(); ProductScreen.do.clickRefund(); TicketScreen.do.selectOrder("-0001"); - TicketScreen.do.clickOrderline("Coca-Cola"); + TicketScreen.check.hasLine({ + productName: "Coca-Cola", + }); ProductScreen.do.pressNumpad("2"); TicketScreen.check.toRefundTextContains("To Refund: 2.00"); TicketScreen.do.confirmRefund(); diff --git a/addons/pos_restaurant/static/tests/tours/SplitBillScreen.tour.js b/addons/pos_restaurant/static/tests/tours/SplitBillScreen.tour.js index 583dd55c556..55c8c807f85 100644 --- a/addons/pos_restaurant/static/tests/tours/SplitBillScreen.tour.js +++ b/addons/pos_restaurant/static/tests/tours/SplitBillScreen.tour.js @@ -4,6 +4,7 @@ import { PaymentScreen } from "@point_of_sale/../tests/tours/helpers/PaymentScre import { ReceiptScreen } from "@point_of_sale/../tests/tours/helpers/ReceiptScreenTourMethods"; import { Chrome } from "@pos_restaurant/../tests/tours/helpers/ChromeTourMethods"; import { FloorScreen } from "@pos_restaurant/../tests/tours/helpers/FloorScreenTourMethods"; +import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods"; import { ProductScreen } from "@pos_restaurant/../tests/tours/helpers/ProductScreenTourMethods"; import { SplitBillScreen } from "@pos_restaurant/../tests/tours/helpers/SplitBillScreenTourMethods"; import { TicketScreen } from "@point_of_sale/../tests/tours/helpers/TicketScreenTourMethods"; @@ -92,7 +93,7 @@ registry.category("web_tour.tours").add("SplitBillScreenTour2", { Chrome.do.clickTicketButton(); TicketScreen.do.selectOrder("-0001"); TicketScreen.do.loadSelectedOrder(); - ProductScreen.do.clickOrderline("Minute Maid", "1.0"); + Order.hasLine({ productName: "Minute Maid", quantity: "1.0", withClass: ".selected" }); ProductScreen.check.totalAmountIs("2.00"); return getSteps(); }, @@ -125,7 +126,7 @@ registry.category("web_tour.tours").add("SplitBillScreenTour3", { // Check if there is still water in the order ProductScreen.check.isShown(); - ProductScreen.do.clickOrderline("Water", "1.0"); + ProductScreen.check.selectedOrderlineHas("Water", "1.0"); ProductScreen.do.clickPayButton(true); PaymentScreen.do.clickPaymentMethod("Bank"); PaymentScreen.do.clickValidate(); @@ -184,7 +185,7 @@ registry.category("web_tour.tours").add("SplitBillScreenTour4PosCombo", { // Check if there is still water in the order ...ProductScreen.check.isShown(), // now we check that all the lines that remained in the order are correct - ...ProductScreen.do.clickOrderline("Minute Maid", "1.0"), + ...ProductScreen.check.selectedOrderlineHas("Minute Maid", "1.0"), ...ProductScreen.do.clickOrderline("Office Combo"), ...ProductScreen.do.clickOrderline("Combo Product 2"), ...ProductScreen.check.selectedOrderlineHas( diff --git a/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js b/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js index fa06dd57f83..b72195a990e 100644 --- a/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js +++ b/addons/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js @@ -2,6 +2,7 @@ import { createTourMethods } from "@point_of_sale/../tests/tours/helpers/utils"; import { Do, Check, Execute } from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods"; +import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods"; class DoExt extends Do { clickSplitBillButton() { @@ -13,13 +14,7 @@ class DoExt extends Do { ]; } doubleClickOrderline(name) { - return [ - { - content: "first click orderline", - trigger: `.order .orderline .product-name:contains("${name}")`, - run: "dblclick" - }, - ]; + return Order.hasLine({ productName: name, run: "dblclick" }); } clickTransferButton() { return [ @@ -77,13 +72,7 @@ class DoExt extends Do { class CheckExt extends Check { orderlinesHaveNoChange() { - return [ - { - content: "Orderlines have no change", - trigger: ".orderlines .orderline:not(.has-change)", - run: function () {}, - }, - ]; + return Order.doesNotHaveLine({ withClass: ".has-change" }); } isPrintingError() { // because we don't have printer in the test. @@ -95,36 +84,16 @@ class CheckExt extends Check { ]; } orderlineIsToOrder(name) { - return [ - { - content: `Line is to order`, - trigger: `.order .orderline.has-change .product-name:contains("${name}")`, - run: function () {}, // it's a check - }, - ]; + return Order.hasLine({ + productName: name, + withClass: ".has-change.text-success.border-start.border-success.border-4", + }); } 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 [ - { - 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}' note`, - trigger: `.order .orderline .info-list .orderline-note:contains("${note}")`, - run: function () {}, // it's a check - }, - ]; + return Order.hasLine({ + withClass: ".skip-change.text-primary.border-start.border-primary.border-4", + productName: name, + }); } guestNumberIs(numberInString) { return [ diff --git a/addons/pos_restaurant/static/tests/tours/helpers/SplitBillScreenTourMethods.js b/addons/pos_restaurant/static/tests/tours/helpers/SplitBillScreenTourMethods.js index 752c279b6d3..f58bfb90f37 100644 --- a/addons/pos_restaurant/static/tests/tours/helpers/SplitBillScreenTourMethods.js +++ b/addons/pos_restaurant/static/tests/tours/helpers/SplitBillScreenTourMethods.js @@ -1,19 +1,11 @@ /** @odoo-module */ import { createTourMethods } from "@point_of_sale/../tests/tours/helpers/utils"; +import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods"; class Do { - clickOrderline(name, totalQuantity) { - let trigger = `.splitbill-screen li.orderline .product-name:contains("${name}")`; - if (totalQuantity) { - trigger += ` ~ .info-list .info:contains("${totalQuantity}")`; - } - return [ - { - content: `click '${name}' orderline with total quantity of '${totalQuantity}'`, - trigger, - }, - ]; + clickOrderline(productName) { + return Order.hasLine({ productName, run: "click" }); } clickBack() { return [ @@ -35,18 +27,10 @@ class Do { class Check { orderlineHas(name, totalQuantity, splitQuantity) { - return [ - { - content: `'${name}' orderline has total quantity of '${totalQuantity}'`, - trigger: `.splitbill-screen li.orderline .product-name:contains("${name}") ~ .info-list .info:contains("${totalQuantity}")`, - run: () => {}, - }, - { - content: `'${name}' orderline has '${splitQuantity}' quantity to split`, - trigger: `.splitbill-screen li.orderline .product-name:contains("${name}") ~ .info-list .info em:contains("${splitQuantity}")`, - run: () => {}, - }, - ]; + return Order.hasLine({ + productName: name, + quantity: splitQuantity != 0 ? `${splitQuantity} / ${totalQuantity}` : totalQuantity, + }); } subtotalIs(amount) { return [ diff --git a/addons/pos_restaurant/static/tests/tours/pos_restaurant.js b/addons/pos_restaurant/static/tests/tours/pos_restaurant.js index 727fb4cfa81..973dcd74dc9 100644 --- a/addons/pos_restaurant/static/tests/tours/pos_restaurant.js +++ b/addons/pos_restaurant/static/tests/tours/pos_restaurant.js @@ -28,8 +28,8 @@ registry.category("web_tour.tours").add("pos_restaurant_sync", { ProductScreen.check.orderlineIsToOrder("Water"); ProductScreen.check.orderlineIsToSkip("Coca-Cola"); ProductScreen.do.clickOrderButton(); - ProductScreen.check.orderlinesHaveNoChange(); ProductScreen.check.isPrintingError(); + ProductScreen.check.orderlinesHaveNoChange(); ProductScreen.check.totalAmountIs("4.40"); // Create 2nd order (paid) diff --git a/addons/pos_sale/static/src/overrides/components/product_screen/orderline/orderline.xml b/addons/pos_sale/static/src/overrides/components/product_screen/orderline/orderline.xml deleted file mode 100644 index e5f8c89fc53..00000000000 --- a/addons/pos_sale/static/src/overrides/components/product_screen/orderline/orderline.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - -
  • - - -
  • - - - - - - - - - -
    x - - : (tax incl.)
    -
    -
    -
    - -
    diff --git a/addons/pos_sale/static/src/overrides/components/product_screen/product_screen.xml b/addons/pos_sale/static/src/overrides/components/product_screen/product_screen.xml new file mode 100644 index 00000000000..0fec2764922 --- /dev/null +++ b/addons/pos_sale/static/src/overrides/components/product_screen/product_screen.xml @@ -0,0 +1,24 @@ + + + + + + +
  • + + +
  • + + + + + + +
    x + : (tax incl.)
    +
    +
    +
    + +
    diff --git a/addons/pos_sale/static/tests/tours/PosSaleTour.js b/addons/pos_sale/static/tests/tours/PosSaleTour.js index a9c58f45713..046a591d346 100644 --- a/addons/pos_sale/static/tests/tours/PosSaleTour.js +++ b/addons/pos_sale/static/tests/tours/PosSaleTour.js @@ -105,7 +105,7 @@ registry // Filter should be automatically 'Paid'. TicketScreen.check.filterIs('Paid'); TicketScreen.do.selectOrder('-0001'); - TicketScreen.do.clickOrderline('Down Payment'); + TicketScreen.check.hasLine({productName: 'Down Payment', withClass: '.selected', quantity: '1.0'}); ProductScreen.do.pressNumpad('1'); TicketScreen.do.confirmRefund(); ProductScreen.do.clickPayButton(); @@ -150,7 +150,6 @@ registry ProductScreen.do.confirmOpeningPopup(); ProductScreen.do.clickQuotationButton(); ProductScreen.do.selectFirstOrder(); - ProductScreen.do.clickOrderline("Product A", "1"); ProductScreen.check.selectedOrderlineHas('Product A', '1.00'); ProductScreen.do.clickPayButton(); PaymentScreen.do.clickPaymentMethod('Bank');