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