19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:29:53 +01:00
parent 6e54c1af6c
commit 3ca647e428
1087 changed files with 132065 additions and 108499 deletions

View file

@ -1,62 +0,0 @@
odoo.define('pos_restaurant.tour.ControlButtons', function (require) {
'use strict';
const { TextAreaPopup } = require('point_of_sale.tour.TextAreaPopupTourMethods');
const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
const { SplitBillScreen } = require('pos_restaurant.tour.SplitBillScreenTourMethods');
const { BillScreen } = require('pos_restaurant.tour.BillScreenTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
var Tour = require('web_tour.tour');
// signal to start generating steps
// when finished, steps can be taken from getSteps
startSteps();
// Test TransferOrderButton
FloorScreen.do.clickTable('T2');
ProductScreen.exec.addOrderline('Water', '5', '2', '10.0');
ProductScreen.do.clickTransferButton();
FloorScreen.do.clickTable('T4');
ProductScreen.do.clickOrderline('Water', '5', '2');
Chrome.do.backToFloor();
FloorScreen.do.clickTable('T2');
ProductScreen.check.orderIsEmpty();
Chrome.do.backToFloor();
FloorScreen.do.clickTable('T4');
ProductScreen.do.clickOrderline('Water', '5', '2');
// Test SplitBillButton
ProductScreen.do.clickSplitBillButton();
SplitBillScreen.do.clickBack();
// Test OrderlineNoteButton
ProductScreen.do.clickNoteButton();
TextAreaPopup.check.isShown();
TextAreaPopup.do.inputText('test note');
TextAreaPopup.do.clickConfirm();
ProductScreen.check.orderlineHasNote('Water', '5', 'test note');
ProductScreen.exec.addOrderline('Water', '8', '1', '8.0');
// Test PrintBillButton
ProductScreen.do.clickPrintBillButton();
BillScreen.check.isShown();
BillScreen.do.clickOk();
// Test GuestButton
ProductScreen.do.clickGuestButton();
NumberPopup.do.pressNumpad('1 5');
NumberPopup.check.inputShownIs('15');
NumberPopup.do.clickConfirm();
ProductScreen.check.guestNumberIs('15')
ProductScreen.do.clickGuestButton();
NumberPopup.do.pressNumpad('5');
NumberPopup.check.inputShownIs('5');
NumberPopup.do.clickConfirm();
ProductScreen.check.guestNumberIs('5')
Tour.register('ControlButtonsTour', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -1,118 +0,0 @@
odoo.define('pos_restaurant.tour.FloorScreen', function (require) {
'use strict';
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
const { TextInputPopup } = require('point_of_sale.tour.TextInputPopupTourMethods');
const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
var Tour = require('web_tour.tour');
// signal to start generating steps
// when finished, steps can be taken from getSteps
startSteps();
// check floors if they contain their corresponding tables
FloorScreen.check.selectedFloorIs('Main Floor');
FloorScreen.check.hasTable('T2');
FloorScreen.check.hasTable('T4');
FloorScreen.check.hasTable('T5');
FloorScreen.do.clickFloor('Second Floor');
FloorScreen.check.hasTable('T3');
FloorScreen.check.hasTable('T1');
// clicking table in active mode does not open product screen
// instead, table is selected
FloorScreen.do.clickEdit();
FloorScreen.check.editModeIsActive(true);
FloorScreen.do.clickTable('T3');
FloorScreen.check.selectedTableIs('T3');
FloorScreen.do.clickTable('T1');
FloorScreen.check.selectedTableIs('T1');
// switching floor in edit mode deactivates edit mode
FloorScreen.do.clickFloor('Main Floor');
FloorScreen.check.editModeIsActive(false);
FloorScreen.do.clickEdit();
FloorScreen.check.editModeIsActive(true);
// test add table
FloorScreen.do.clickAddTable();
FloorScreen.check.selectedTableIs('T1');
FloorScreen.do.clickRename();
TextInputPopup.check.isShown();
TextInputPopup.do.inputText('T100');
TextInputPopup.do.clickConfirm();
FloorScreen.check.selectedTableIs('T100');
// test duplicate table
FloorScreen.do.clickDuplicate();
// new table is already named T101
FloorScreen.check.selectedTableIs('T101');
FloorScreen.do.clickRename();
TextInputPopup.check.isShown();
TextInputPopup.do.inputText('T1111');
TextInputPopup.do.clickConfirm();
FloorScreen.check.selectedTableIs('T1111');
// switch floor, switch back and check if
// the new tables are still there
FloorScreen.do.clickFloor('Second Floor');
FloorScreen.check.editModeIsActive(false);
FloorScreen.check.hasTable('T3');
FloorScreen.check.hasTable('T1');
FloorScreen.do.clickFloor('Main Floor');
FloorScreen.check.hasTable('T2');
FloorScreen.check.hasTable('T4');
FloorScreen.check.hasTable('T5');
FloorScreen.check.hasTable('T100');
FloorScreen.check.hasTable('T1111');
// test delete table
FloorScreen.do.clickEdit();
FloorScreen.check.editModeIsActive(true);
FloorScreen.do.clickTable('T2');
FloorScreen.check.selectedTableIs('T2');
FloorScreen.do.clickTrash();
Chrome.do.confirmPopup();
// change number of seats
FloorScreen.do.clickTable('T4');
FloorScreen.check.selectedTableIs('T4');
FloorScreen.do.clickSeats();
NumberPopup.do.pressNumpad('Backspace 9');
NumberPopup.check.inputShownIs('9');
NumberPopup.do.clickConfirm();
FloorScreen.check.tableSeatIs('T4', '9');
// change number of seat when the input is already selected
FloorScreen.do.clickTable('T4');
FloorScreen.check.selectedTableIs('T4');
FloorScreen.do.clickSeats();
NumberPopup.do.pressNumpad('1 5');
NumberPopup.check.inputShownIs('15');
NumberPopup.do.clickConfirm();
FloorScreen.check.tableSeatIs('T4', '15');
// change shape
FloorScreen.do.changeShapeTo('round');
// Opening product screen in main floor should go back to main floor
FloorScreen.do.clickEdit();
FloorScreen.check.editModeIsActive(false);
FloorScreen.check.tableIsNotSelected('T4');
FloorScreen.do.clickTable('T4');
ProductScreen.check.isShown();
Chrome.check.backToFloorTextIs('Main Floor', 'T4');
Chrome.do.backToFloor();
// Opening product screen in second floor should go back to second floor
FloorScreen.do.clickFloor('Second Floor');
FloorScreen.check.hasTable('T3');
FloorScreen.do.clickTable('T3');
Chrome.check.backToFloorTextIs('Second Floor', 'T3');
Tour.register('FloorScreenTour', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -1,80 +0,0 @@
odoo.define('pos_restaurant.tour.SplitBillScreen', function (require) {
'use strict';
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
const { SplitBillScreen } = require('pos_restaurant.tour.SplitBillScreenTourMethods');
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
var Tour = require('web_tour.tour');
// signal to start generating steps
// when finished, steps can be taken from getSteps
startSteps();
FloorScreen.do.clickTable('T2');
ProductScreen.do.confirmOpeningPopup();
ProductScreen.exec.addOrderline('Water', '5', '2', '10.0');
ProductScreen.exec.addOrderline('Minute Maid', '3', '2', '6.0');
ProductScreen.exec.addOrderline('Coca-Cola', '1', '2', '2.0');
ProductScreen.do.clickSplitBillButton();
// Check if the screen contains all the orderlines
SplitBillScreen.check.orderlineHas('Water', '5', '0');
SplitBillScreen.check.orderlineHas('Minute Maid', '3', '0');
SplitBillScreen.check.orderlineHas('Coca-Cola', '1', '0');
// split 3 water and 1 coca-cola
SplitBillScreen.do.clickOrderline('Water');
SplitBillScreen.check.orderlineHas('Water', '5', '1');
SplitBillScreen.do.clickOrderline('Water');
SplitBillScreen.do.clickOrderline('Water');
SplitBillScreen.check.orderlineHas('Water', '5', '3');
SplitBillScreen.check.subtotalIs('6.0')
SplitBillScreen.do.clickOrderline('Coca-Cola');
SplitBillScreen.check.orderlineHas('Coca-Cola', '1', '1');
SplitBillScreen.check.subtotalIs('8.0')
// click pay to split, go back to check the lines
SplitBillScreen.do.clickPay();
PaymentScreen.do.clickBack();
ProductScreen.do.clickOrderline('Water', '3.0')
ProductScreen.do.clickOrderline('Coca-Cola', '1.0')
// go back to the original order and see if the order is changed
Chrome.do.clickTicketButton();
TicketScreen.do.selectOrder('-0001');
ProductScreen.check.isShown()
ProductScreen.do.clickOrderline('Water', '2.0')
ProductScreen.do.clickOrderline('Minute Maid', '3.0')
Tour.register('SplitBillScreenTour', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
FloorScreen.do.clickTable('T2');
ProductScreen.exec.addOrderline('Water', '1', '2.0');
ProductScreen.exec.addOrderline('Minute Maid', '1', '2.0');
ProductScreen.exec.addOrderline('Coca-Cola', '1', '2.0');
Chrome.do.backToFloor();
FloorScreen.do.clickTable('T2');
ProductScreen.do.clickSplitBillButton();
SplitBillScreen.do.clickOrderline('Water');
SplitBillScreen.do.clickOrderline('Coca-Cola');
SplitBillScreen.do.clickPay();
PaymentScreen.do.clickBack();
Chrome.do.clickTicketButton();
TicketScreen.do.selectOrder('-0002');
ProductScreen.do.clickOrderline('Water', '1.0');
ProductScreen.do.clickOrderline('Coca-Cola', '1.0');
ProductScreen.check.totalAmountIs('4.40');
Chrome.do.clickTicketButton();
TicketScreen.do.selectOrder('-0001');
ProductScreen.do.clickOrderline('Minute Maid', '1.0');
ProductScreen.check.totalAmountIs('2.20');
Tour.register('SplitBillScreenTour2', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -1,63 +0,0 @@
odoo.define('pos_restaurant.tour.TicketScreen', function (require) {
'use strict';
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
var Tour = require('web_tour.tour');
startSteps();
// New Ticket button should not be in the ticket screen if no table is selected.
Chrome.do.clickTicketButton();
TicketScreen.check.noNewTicketButton();
TicketScreen.do.clickDiscard();
// Deleting the last order in the table brings back to floorscreen
FloorScreen.do.clickTable('T4');
ProductScreen.do.confirmOpeningPopup();
ProductScreen.check.isShown();
Chrome.do.clickTicketButton();
TicketScreen.check.nthRowContains(2, '-0001');
TicketScreen.do.deleteOrder('-0001');
FloorScreen.check.isShown();
// Create 2 items in a table. From floorscreen, delete 1 item. Then select the other item.
// Correct order and screen should be displayed and the BackToFloorButton is shown.
FloorScreen.do.clickTable('T2');
ProductScreen.exec.addOrderline('Minute Maid', '1', '2');
ProductScreen.check.totalAmountIs('2.0');
Chrome.do.clickTicketButton();
TicketScreen.do.clickNewTicket();
ProductScreen.exec.addOrderline('Coca-Cola', '2', '2');
ProductScreen.check.totalAmountIs('4.0');
Chrome.do.backToFloor();
FloorScreen.check.orderCountSyncedInTableIs('T2', '2');
Chrome.do.clickTicketButton();
TicketScreen.do.deleteOrder('-0003');
Chrome.do.confirmPopup();
TicketScreen.do.selectOrder('-0002');
ProductScreen.check.isShown();
ProductScreen.check.totalAmountIs('2.0');
Chrome.check.backToFloorTextIs('Main Floor', 'T2');
Chrome.do.backToFloor();
// Make sure that order is deleted properly.
FloorScreen.do.clickTable('T5');
ProductScreen.exec.addOrderline('Minute Maid', '1', '3');
ProductScreen.check.totalAmountIs('3.0');
Chrome.do.backToFloor();
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
Chrome.do.clickTicketButton();
TicketScreen.do.deleteOrder('-0004');
Chrome.do.confirmPopup();
TicketScreen.do.clickDiscard();
FloorScreen.check.isShown();
FloorScreen.check.orderCountSyncedInTableIs('T5', '0');
FloorScreen.do.clickTable('T5');
ProductScreen.check.orderIsEmpty();
Tour.register('PosResTicketScreenTour', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -1,127 +0,0 @@
odoo.define('pos_restaurant.tour.TipScreen', function (require) {
'use strict';
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
const { TipScreen } = require('pos_restaurant.tour.TipScreenTourMethods');
const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
var Tour = require('web_tour.tour');
startSteps();
// Create order that is synced when draft.
// order 1
FloorScreen.do.clickTable('T2');
ProductScreen.do.confirmOpeningPopup();
ProductScreen.exec.addOrderline('Minute Maid', '1', '2');
ProductScreen.check.totalAmountIs('2.0');
Chrome.do.backToFloor();
FloorScreen.check.orderCountSyncedInTableIs('T2', '1');
FloorScreen.do.clickTable('T2');
ProductScreen.check.totalAmountIs('2.0');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.do.clickValidate();
TipScreen.check.isShown();
Chrome.do.clickTicketButton();
TicketScreen.do.clickNewTicket();
// order 2
ProductScreen.exec.addOrderline('Coca-Cola', '2', '2');
ProductScreen.check.totalAmountIs('4.0');
Chrome.do.backToFloor();
FloorScreen.check.orderCountSyncedInTableIs('T2', '1');
Chrome.do.clickTicketButton();
TicketScreen.check.nthRowContains('2', 'Tipping');
TicketScreen.do.clickDiscard();
// Create without syncing the draft.
// order 3
FloorScreen.do.clickTable('T5');
ProductScreen.exec.addOrderline('Minute Maid', '3', '2');
ProductScreen.check.totalAmountIs('6.0');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.do.clickValidate();
TipScreen.check.isShown();
Chrome.do.clickTicketButton();
TicketScreen.do.clickNewTicket();
// order 4
ProductScreen.exec.addOrderline('Coca-Cola', '4', '2');
ProductScreen.check.totalAmountIs('8.0');
Chrome.do.backToFloor();
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
Chrome.do.clickTicketButton();
TicketScreen.check.nthRowContains('3', 'Tipping');
// Tip 20% on order1
TicketScreen.do.selectOrder('-0001');
TipScreen.check.isShown();
TipScreen.check.totalAmountIs('2.0');
TipScreen.check.percentAmountIs('15%', '0.30');
TipScreen.check.percentAmountIs('20%', '0.40');
TipScreen.check.percentAmountIs('25%', '0.50');
TipScreen.do.clickPercentTip('20%');
TipScreen.check.inputAmountIs('0.40')
Chrome.do.backToFloor();
FloorScreen.check.isShown();
Chrome.do.clickTicketButton();
// Tip 25% on order3
TicketScreen.do.selectOrder('-0003');
TipScreen.check.isShown();
TipScreen.check.totalAmountIs('6.0');
TipScreen.check.percentAmountIs('15%', '0.90');
TipScreen.check.percentAmountIs('20%', '1.20');
TipScreen.check.percentAmountIs('25%', '1.50');
TipScreen.do.clickPercentTip('25%');
TipScreen.check.inputAmountIs('1.50');
Chrome.do.backToFloor();
FloorScreen.check.isShown();
Chrome.do.clickTicketButton();
// finalize order 4 then tip custom amount
TicketScreen.do.selectOrder('-0004');
ProductScreen.check.isShown();
ProductScreen.check.totalAmountIs('8.0');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.do.clickValidate();
TipScreen.check.isShown();
TipScreen.check.totalAmountIs('8.0');
TipScreen.check.percentAmountIs('15%', '1.20');
TipScreen.check.percentAmountIs('20%', '1.60');
TipScreen.check.percentAmountIs('25%', '2.00');
TipScreen.do.setCustomTip('1.00');
TipScreen.check.inputAmountIs('1.00')
Chrome.do.backToFloor();
FloorScreen.check.isShown();
// settle tips here
Chrome.do.clickTicketButton();
TicketScreen.do.selectFilter('Tipping');
TicketScreen.check.tipContains('1.00');
TicketScreen.do.settleTips();
TicketScreen.do.selectFilter('All active orders');
TicketScreen.check.nthRowContains(2, 'Ongoing');
// tip order2 during payment
// tip screen should not show after validating payment screen
TicketScreen.do.selectOrder('-0002');
ProductScreen.check.isShown();
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickTipButton();
NumberPopup.check.isShown();
NumberPopup.do.pressNumpad('1');
NumberPopup.check.inputShownIs('1');
NumberPopup.do.clickConfirm();
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.do.clickValidate();
ReceiptScreen.check.isShown();
Tour.register('PosResTipScreenTour', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -0,0 +1,112 @@
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
import * as NumberPopup from "@point_of_sale/../tests/generic_helpers/number_popup_util";
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
import * as SplitBillScreen from "@pos_restaurant/../tests/tours/utils/split_bill_screen_util";
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
import * as ChromePos from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import * as ChromeRestaurant from "@pos_restaurant/../tests/tours/utils/chrome";
const Chrome = { ...ChromePos, ...ChromeRestaurant };
import { registry } from "@web/core/registry";
import { delay } from "@web/core/utils/concurrency";
registry.category("web_tour.tours").add("ControlButtonsTour", {
steps: () =>
[
// Test merging table, transfer is already tested in pos_restaurant_sync_second_login.
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Water", "5", "2", "10.0"),
Chrome.clickPlanButton(),
FloorScreen.clickTable("4"),
ProductScreen.addOrderline("Minute Maid", "3", "2", "6.0"),
// Extra line is added to test merging table.
// Merging this order to another should also include this extra line.
ProductScreen.clickDisplayedProduct("Coca-Cola"),
ProductScreen.selectedOrderlineHas("Coca-Cola", "1"),
ProductScreen.clickControlButton("Transfer"),
{
trigger: ".table:contains(2)",
async run(helpers) {
await delay(500);
await helpers.click();
},
},
Order.hasLine({ productName: "Water", quantity: "5" }),
Order.hasLine({ productName: "Minute Maid", quantity: "3" }),
Order.hasLine({ productName: "Coca-Cola", quantity: "1" }),
// Test SplitBillButton
ProductScreen.clickControlButton("Split"),
SplitBillScreen.clickBack(),
ProductScreen.clickLine("Water", "5"),
ProductScreen.addInternalNote("test note", "Note"),
Order.hasLine({
productName: "Water",
quantity: "5",
price: "10.0",
internalNote: "test note",
withClass: ".selected",
}),
// Check that note is imported if come back to the table
Chrome.clickPlanButton(),
FloorScreen.clickTable("2"),
Order.hasLine({
productName: "Water",
quantity: "5",
price: "10.0",
internalNote: "test note",
}),
ProductScreen.addOrderline("Water", "8", "1", "8.0"),
// Test GuestButton
ProductScreen.clickControlButton("Guests"),
{
content: `click numpad button: 1`,
trigger: ".modal div.numpad button:text(1)",
run: "click",
},
{
content: `click numpad button: 5`,
trigger: ".modal div.numpad button:text(5)",
run: "click",
},
NumberPopup.isShown("15"),
Dialog.confirm(),
ProductScreen.guestNumberIs("15"),
{
content: `click guests 15 button`,
trigger: `.modal .control-buttons button:contains(15Guests)`,
run: "click",
},
{
content: `click numpad button: 5`,
trigger: ".modal div.numpad button:text(5)",
run: "click",
},
NumberPopup.isShown("5"),
Dialog.confirm(),
ProductScreen.guestNumberIs("5"),
// Test Cancel Order Button
Dialog.cancel(),
Order.hasLine({ productName: "Water", quantity: "5" }),
ProductScreen.clickControlButton("Cancel Order"),
Dialog.confirm(),
Order.doesNotHaveLine(),
FloorScreen.isShown(),
// Test moving order to a table on a different floor
FloorScreen.clickTable("5"),
ProductScreen.addOrderline("Water", "5", "2", "10.0"),
ProductScreen.clickControlButton("Transfer"),
FloorScreen.clickFloor("Second Floor"),
FloorScreen.clickTable("1"),
Order.hasLine({ productName: "Water", quantity: "5" }),
].flat(),
});

View file

@ -0,0 +1,119 @@
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
import * as ChromePos from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import * as ChromeRestaurant from "@pos_restaurant/../tests/tours/utils/chrome";
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as DeviceSynchronization from "@pos_restaurant/../tests/tours/utils/devices_synchronization";
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
import { registry } from "@web/core/registry";
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
const Chrome = { ...ChromePos, ...ChromeRestaurant };
registry.category("web_tour.tours").add("test_devices_synchronization", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("5"),
// product_screen
// Check if lines created from another devices
// correctly appear in the current device
ProductScreen.orderlineIsToOrder("Coca-Cola"),
DeviceSynchronization.createNewLine("Water", 2),
ProductScreen.orderlineIsToOrder("Water"),
DeviceSynchronization.changeLineQuantity("Water", 44),
ProductScreen.checkTotalAmount(99.0),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("Acme Corporation"),
Chrome.clickPlanButton(),
// prpoduct_screen
// Check if changing partner form another device
// correctly appear in the current device
FloorScreen.clickTable("5"),
DeviceSynchronization.changePartner("Lumber Inc"),
ProductScreen.customerIs("Lumber Inc"),
// prpoduct_screen
// Check if paying from another device
// is correctly synchronized
DeviceSynchronization.markOrderAsPaid(),
ProductScreen.orderIsEmpty(),
// floor_screen
// Check if floor plan is correctly updated
// when creating a new order from another device
DeviceSynchronization.createNewOrderOnTable("5", [
["Coca-Cola", 1],
["Water", 2],
]),
DeviceSynchronization.createNewOrderOnTable("4", [
["Coca-Cola", 50],
["Water", 30],
]),
Chrome.clickPlanButton(),
FloorScreen.orderCountSyncedInTableIs("5", 3),
FloorScreen.orderCountSyncedInTableIs("4", 80),
FloorScreen.clickTable("5"),
ProductScreen.checkTotalAmount(6.6),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
Chrome.clickPlanButton(),
FloorScreen.clickTable("4"),
ProductScreen.checkTotalAmount(176.0),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
// product_screen
// Check if creating an order one same table from two devices
// is correctly handlded
FloorScreen.clickTable("5"),
ProductScreen.clickDisplayedProduct("Coca-Cola"),
DeviceSynchronization.createNewOrderOnTable(
"5",
[
["Coca-Cola", 2],
["Water", 2],
],
false
),
ProductScreen.orderLineHas("Coca-Cola", "2"),
ProductScreen.orderLineHas("Water", "2"),
ProductScreen.clickDisplayedProduct("Water"),
ProductScreen.clickLine("Coca-Cola", 2),
ProductScreen.clickLine("Coca-Cola", 1),
ProductScreen.clickLine("Water", 3),
].flat(),
});
registry.category("web_tour.tours").add("OrderSynchronisationTour", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
DeviceSynchronization.createNewOrderOnTable("4", [
["Coca-Cola", 50],
["Water", 30],
]),
FloorScreen.clickTable("4"),
ProductScreen.orderLineHas("Coca-Cola", "50.0"),
DeviceSynchronization.markOrderAsPaid(),
ProductScreen.isShown(),
Chrome.clickOrders(),
TicketScreen.selectFilter("Paid"),
TicketScreen.checkStatus("device_sync", "Paid"),
TicketScreen.selectOrder("device_sync"),
TicketScreen.confirmRefund(),
].flat(),
});

View file

@ -0,0 +1,67 @@
/* global posmodel */
import { registry } from "@web/core/registry";
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as ChromePos from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import * as ChromeRestaurant from "@pos_restaurant/../tests/tours/utils/chrome";
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
const Chrome = { ...ChromePos, ...ChromeRestaurant };
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
const getRandomTable = () => {
const tables = posmodel.currentFloor.table_ids;
return tables[Math.floor(Math.random() * tables.length)].table_number;
};
const getRandomTableWithOrder = () => {
const tables = posmodel.currentFloor.table_ids.filter(
(table) => table.backLink("<-pos.order.table_id").length > 0
);
return tables[Math.floor(Math.random() * tables.length)].table_number;
};
const getRandomProduct = () => {
const products = posmodel.models["product.product"].filter(
(p) =>
!p.isConfigurable() &&
!p.isCombo() &&
!p.isTracked() &&
p.id !== posmodel.config.tip_product_id?.id &&
!posmodel.config._pos_special_products_ids?.includes(p.id)
);
return products[Math.floor(Math.random() * products.length)].name;
};
registry.category("web_tour.tours").add("PoSFakeTourRestaurant", {
steps: () =>
[
FloorScreen.clickTable(getRandomTable()),
ProductScreen.clickDisplayedProduct(getRandomProduct()),
ProductScreen.clickDisplayedProduct(getRandomProduct()),
ProductScreen.clickDisplayedProduct(getRandomProduct()),
Chrome.clickPlanButton(),
FloorScreen.clickTable(getRandomTable()),
ProductScreen.clickDisplayedProduct(getRandomProduct()),
ProductScreen.clickDisplayedProduct(getRandomProduct()),
ProductScreen.clickDisplayedProduct(getRandomProduct()),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
].flat(),
});
registry.category("web_tour.tours").add("PoSFakeTourTransferOrder", {
steps: () =>
[
FloorScreen.clickTable(getRandomTableWithOrder()),
ProductScreen.clickControlButton("Transfer"),
FloorScreen.clickTable(getRandomTable()),
ProductScreen.clickDisplayedProduct(getRandomProduct()),
Chrome.clickPlanButton(),
].flat(),
});

View file

@ -0,0 +1,356 @@
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
import * as NumberPopup from "@point_of_sale/../tests/generic_helpers/number_popup_util";
import * as ChromePos from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import * as ChromeRestaurant from "@pos_restaurant/../tests/tours/utils/chrome";
const Chrome = { ...ChromePos, ...ChromeRestaurant };
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
import * as Utils from "@point_of_sale/../tests/generic_helpers/utils";
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
import { registry } from "@web/core/registry";
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
registry.category("web_tour.tours").add("FloorScreenTour", {
steps: () =>
[
// check floors if they contain their corresponding tables
Chrome.startPoS(),
FloorScreen.selectedFloorIs("Main Floor"),
FloorScreen.hasTable("2"),
FloorScreen.hasTable("4"),
FloorScreen.hasTable("5"),
FloorScreen.clickFloor("Second Floor"),
FloorScreen.hasTable("3"),
FloorScreen.hasTable("1"),
// clicking table in active mode does not open product screen
// instead, table is selected
Chrome.clickMenuOption("Edit Plan"),
FloorScreen.clickTable("3"),
FloorScreen.selectedTableIs("3"),
FloorScreen.clickTable("1"),
FloorScreen.selectedTableIs("1"),
//test copy floor
FloorScreen.clickFloor("Main Floor"),
FloorScreen.clickEditButton("Clone"),
FloorScreen.selectedFloorIs("Main Floor (copy)"),
FloorScreen.hasTable("2"),
FloorScreen.hasTable("4"),
FloorScreen.hasTable("5"),
Utils.refresh(),
Chrome.clickMenuOption("Edit Plan"),
FloorScreen.clickFloor("Main Floor (copy)"),
FloorScreen.hasTable("2"),
FloorScreen.hasTable("4"),
FloorScreen.hasTable("5"),
FloorScreen.clickEditButton("Delete"),
Dialog.confirm(),
Utils.refresh(),
Chrome.clickMenuOption("Edit Plan"),
Utils.elementDoesNotExist(
".floor-selector .button-floor:contains('Main Floor (copy)')"
),
// test add table
FloorScreen.clickFloor("Main Floor"),
{
trigger: `.edit-buttons i[aria-label="Add Table"]`,
run: "click",
},
FloorScreen.selectedTableIs("6"),
FloorScreen.clickEditButton("Rename"),
NumberPopup.enterValue("100"),
NumberPopup.isShown("100"),
Dialog.confirm(),
FloorScreen.selectedTableIs("100"),
// test duplicate table
FloorScreen.clickEditButton("Clone"),
// the name is the first number available on the floor
FloorScreen.selectedTableIs("1"),
FloorScreen.clickEditButton("Rename"),
NumberPopup.enterValue("1111"),
NumberPopup.isShown("1111"),
Dialog.confirm(),
FloorScreen.selectedTableIs("1111"),
// switch floor, switch back and check if
// the new tables are still there
FloorScreen.clickFloor("Second Floor"),
FloorScreen.hasTable("3"),
FloorScreen.hasTable("1"),
//test duplicate multiple tables
FloorScreen.clickTable("1"),
FloorScreen.selectedTableIs("1"),
FloorScreen.ctrlClickTable("3"),
FloorScreen.selectedTableIs("3"),
FloorScreen.clickEditButton("Clone"),
FloorScreen.selectedTableIs("4"),
FloorScreen.selectedTableIs("5"),
//test delete multiple tables
FloorScreen.clickEditButton("Delete"),
Dialog.confirm(),
FloorScreen.clickFloor("Main Floor"),
FloorScreen.hasTable("2"),
FloorScreen.hasTable("4"),
FloorScreen.hasTable("5"),
FloorScreen.hasTable("100"),
FloorScreen.hasTable("1111"),
// test delete table
FloorScreen.clickTable("1111"),
FloorScreen.selectedTableIs("1111"),
FloorScreen.clickEditButton("Delete"),
Dialog.confirm(),
// change number of seats
FloorScreen.clickTable("4"),
FloorScreen.selectedTableIs("4"),
FloorScreen.clickEditButton("Seats"),
NumberPopup.enterValue("⌫9"),
NumberPopup.enterValue("9"),
NumberPopup.isShown("9"),
Dialog.confirm(),
FloorScreen.table({ name: "4" }),
// change number of seat when the input is already selected
FloorScreen.selectedTableIs("4"),
FloorScreen.clickEditButton("Seats"),
NumberPopup.enterValue("15"),
NumberPopup.isShown("15"),
Dialog.confirm(),
FloorScreen.table({ name: "4" }),
// change shape
FloorScreen.clickEditButton("Make Round"),
// Opening product screen in main floor should go back to main floor
FloorScreen.clickSaveEditButton(),
FloorScreen.table({ name: "4", withoutClass: ".selected" }),
FloorScreen.clickTable("4"),
ProductScreen.isShown(),
Chrome.clickPlanButton(),
// Opening product screen in second floor should go back to second floor
FloorScreen.clickFloor("Second Floor"),
FloorScreen.hasTable("3"),
FloorScreen.clickTable("3"),
ProductScreen.isShown(),
Chrome.clickPlanButton(),
FloorScreen.selectedFloorIs("Second Floor"),
].flat(),
});
registry.category("web_tour.tours").add("TableMergeUnmergeTour", {
steps: () =>
[
Chrome.startPoS(),
// Check the linking of tables
FloorScreen.clickFloor("Main Floor"),
FloorScreen.clickTable("4"),
ProductScreen.clickDisplayedProduct("Coca-Cola"),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
FloorScreen.linkTables("5", "4"),
FloorScreen.isChildTable("5"),
Utils.refresh(),
FloorScreen.isChildTable("5"),
// Check that tables are unlinked automatically when the order is done
FloorScreen.clickTable("5"),
Chrome.isTabActive("4 & 5"),
inLeftSide(ProductScreen.orderLineHas("Coca-Cola", "1")),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
FloorScreen.goTo("5"),
Chrome.isTabActive("4 & 5"),
inLeftSide(ProductScreen.orderLineHas("Coca-Cola", "1")),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
Utils.negateStep(FloorScreen.isChildTable("5")),
FloorScreen.linkTables("5", "4"),
// Check that the tables are unlinked when the child table is dragged
FloorScreen.unlinkTables("5", "4"),
Utils.negateStep(FloorScreen.isChildTable("5")),
// Verify that tables are unlinked and original orders are restored after dragging a child table.
FloorScreen.isShown(),
FloorScreen.clickTable("4"),
ProductScreen.clickDisplayedProduct("Coca-Cola"),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
FloorScreen.clickTable("5"),
ProductScreen.clickDisplayedProduct("Minute Maid"),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
// Link tables 5 and 4
FloorScreen.linkTables("5", "4"),
FloorScreen.isChildTable("5"),
// Check merged orders
FloorScreen.clickTable("5"),
Chrome.isTabActive("4 & 5"),
inLeftSide(ProductScreen.orderLineHas("Coca-Cola", "1")),
inLeftSide(ProductScreen.orderLineHas("Minute Maid", "1")),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
// Unlink tables and verify restoration of original orders
FloorScreen.unlinkTables("5", "4"),
Utils.negateStep(FloorScreen.isChildTable("5")),
// Check original orders for table 4
FloorScreen.clickTable("4"),
inLeftSide(ProductScreen.orderLineHas("Coca-Cola", "1")),
ProductScreen.clickOrderButton(),
{
...Dialog.confirm(),
content: "Acknowledge printing error (test does not use a printer).",
},
ProductScreen.orderlinesHaveNoChange(),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
// Check original orders for table 5
FloorScreen.clickTable("5"),
inLeftSide(ProductScreen.orderLineHas("Minute Maid", "1")),
ProductScreen.clickOrderButton(),
{
...Dialog.confirm(),
content: "Acknowledge printing error (test does not use a printer).",
},
ProductScreen.orderlinesHaveNoChange(),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
// Relink tables and verify
FloorScreen.linkTables("5", "4"),
FloorScreen.clickTable("5"),
Chrome.isTabActive("4 & 5"),
ProductScreen.orderlinesHaveNoChange(),
// Add a new product to the merged order
ProductScreen.clickDisplayedProduct("Minute Maid"),
ProductScreen.orderlineIsToOrder("Minute Maid"),
ProductScreen.clickOrderButton(),
{
...Dialog.confirm(),
content: "Acknowledge printing error (test does not use a printer).",
},
ProductScreen.orderlinesHaveNoChange(),
// Unlink tables again and verify restoration
Chrome.clickPlanButton(),
FloorScreen.isShown(),
FloorScreen.unlinkTables("5", "4"),
Utils.negateStep(FloorScreen.isChildTable("5")),
// Verify orders after unlinking
FloorScreen.clickTable("5"),
inLeftSide(ProductScreen.orderLineHas("Minute Maid", "1")),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
FloorScreen.clickTable("4"),
inLeftSide(ProductScreen.orderLineHas("Coca-Cola", "1")),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("test_create_floor_tour", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
Chrome.clickMenuOption("Edit Plan"),
FloorScreen.addFloor("AAA"),
].flat(),
});
registry.category("web_tour.tours").add("test_tax_in_merge_table_order_line_tour", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickFloor("Main Floor"),
FloorScreen.clickTable("4"),
ProductScreen.clickDisplayedProduct("product_1"),
Chrome.clickPlanButton(),
FloorScreen.clickFloor("Main Floor"),
FloorScreen.clickTable("5"),
ProductScreen.clickDisplayedProduct("product_2"),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
FloorScreen.linkTables("5", "4"),
FloorScreen.isChildTable("5"),
].flat(),
});
registry.category("web_tour.tours").add("no_ghost_floor", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
// 1. Create new floor along with a table
Chrome.clickMenuOption("Edit Plan"),
FloorScreen.addFloor("Ghost Floor"),
{
trigger: `.edit-buttons i[aria-label="Add Table"]`,
run: "click",
},
FloorScreen.selectedTableIs("1"),
FloorScreen.clickEditButton("Rename"),
NumberPopup.enterValue("999"),
NumberPopup.isShown("999"),
Dialog.confirm(),
FloorScreen.selectedTableIs("999"),
FloorScreen.clickSaveEditButton(),
// 2. Create and pay an order on that table
FloorScreen.clickFloor("Ghost Floor"),
FloorScreen.clickTable("999"),
ProductScreen.clickDisplayedProduct("Coca-Cola"),
ProductScreen.clickPayButton(false),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
// 3. Delete the floor
Chrome.clickMenuOption("Edit Plan"),
FloorScreen.clickFloor("Ghost Floor"),
FloorScreen.clickEditButton("Delete"),
Dialog.confirm(),
FloorScreen.clickSaveEditButton(),
// 4. Refund one orderline of the paid order
Chrome.clickOrders(),
TicketScreen.selectFilter("Active"),
TicketScreen.selectFilter("Paid"),
TicketScreen.selectOrder("0001"),
TicketScreen.confirmRefund(),
PaymentScreen.isShown(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
// 5. Floor Plan ===> The floor deleted was reappearing
FloorScreen.hasNotFloor("Ghost Floor"),
].flat(),
});

View file

@ -1,56 +0,0 @@
odoo.define('pos_restaurant.tour.BillScreenTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
class Do {
clickOk() {
return [
{
content: `go back`,
trigger: `.receipt-screen .button.next`,
},
];
}
clickBillButton() {
return [
{
content: "click bill button",
trigger: '.control-button:contains("Bill")',
},
];
}
}
class Check {
isShown() {
return [
{
content: 'Bill screen is shown',
trigger: '.receipt-screen h1:contains("Bill Printing")',
run: () => {},
},
];
}
isQRCodeShown() {
return [
{
content: "QR codes are shown",
trigger: '#posqrcode',
run: () => {},
},
];
}
isQRCodeNotShown() {
return [
{
content: "QR codes are shown",
trigger: 'body:not(:has(#posqrcode))',
run: () => {},
},
];
}
}
return createTourMethods('BillScreen', Do, Check);
});

View file

@ -1,33 +0,0 @@
odoo.define('pos_restaurant.tour.ChromeTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
const { Do } = require('point_of_sale.tour.ChromeTourMethods');
class DoExt extends Do {
backToFloor() {
return [
{
content: 'back to floor',
trigger: '.floor-button',
},
];
}
}
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 Execute {}
return createTourMethods('Chrome', DoExt, Check, Execute);
});

View file

@ -1,157 +0,0 @@
odoo.define('pos_restaurant.tour.FloorScreenTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
class Do {
clickTable(name) {
return [
{
content: `click table '${name}'`,
trigger: `.floor-map .table .label:contains("${name}")`,
},
];
}
clickFloor(name) {
return [
{
content: `click '${name}' floor`,
trigger: `.floor-selector .button-floor:contains("${name}")`,
},
];
}
clickEdit() {
return [
{
content: `click edit button`,
trigger: `.floor-map .edit-button`,
},
];
}
clickAddTable() {
return [
{
content: 'add table',
trigger: `.floor-map .edit-button i[aria-label=Add]`,
},
];
}
clickDuplicate() {
return [
{
content: 'duplicate table',
trigger: `.floor-map .edit-button i[aria-label=Duplicate]`,
},
];
}
clickRename() {
return [
{
content: 'rename table',
trigger: `.floor-map .edit-button i[aria-label=Rename]`,
},
];
}
clickSeats() {
return [
{
content: 'change number of seats',
trigger: `.floor-map .edit-button i[aria-label=Seats]`,
},
];
}
clickTrash() {
return [
{
content: 'trash table',
trigger: `.floor-map .edit-button.trash`,
},
];
}
changeShapeTo(shape) {
return [
{
content: `change shape to '${shape}'`,
trigger: `.edit-button .button-option${shape === 'round' ? '.square' : '.round'}`,
},
];
}
}
class Check {
selectedFloorIs(name) {
return [
{
content: `selected floor is '${name}'`,
trigger: `.floor-selector .button-floor.active:contains("${name}")`,
run: () => {},
},
];
}
selectedTableIs(name) {
return [
{
content: `selected table is '${name}'`,
trigger: `.floor-map .table.selected .label:contains("${name}")`,
run: () => {},
},
];
}
hasTable(name) {
return [
{
content: `selected floor has '${name}' table`,
trigger: `.floor-map .tables .table .label:contains("${name}")`,
run: () => {},
},
];
}
editModeIsActive(flag) {
return [
{
content: `check if edit mode is ${flag ? 'active' : 'inactive'}`,
trigger: `.floor-map .edit-button${flag ? '.active' : ':not(:has(.active))'}`,
run: () => {},
},
];
}
tableSeatIs(table, val) {
return [
{
content: `number of seats in table '${table}' is '${val}'`,
trigger: `.floor-map .tables .table .label:contains("${table}") ~ .table-seats:contains("${val}")`,
run: function () {},
},
];
}
orderCountSyncedInTableIs(table, count) {
return [
{
trigger: `.floor-map .table .order-count:contains("${count}") ~ .label:contains("${table}")`,
run: function () {},
},
];
}
isShown() {
return [
{
trigger: '.floor-map',
run: function () {},
},
];
}
tableIsNotSelected(name) {
return [
{
content: `table '${name}' is not selected`,
trigger: `.floor-map .table:not(.selected) .label:contains("${name}")`,
run: function () {},
},
];
}
}
class Execute {}
return createTourMethods('FloorScreen', Do, Check, Execute);
});

View file

@ -1,87 +0,0 @@
odoo.define('pos_restaurant.tour.ProductScreenTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
const { Do, Check, Execute } = require('point_of_sale.tour.ProductScreenTourMethods');
class DoExt extends Do {
clickSplitBillButton() {
return [
{
content: 'click split bill button',
trigger: '.control-buttons .control-button.order-split',
},
];
}
clickTransferButton() {
return [
{
content: 'click transfer button',
trigger: '.control-buttons .control-button span:contains("Transfer")',
},
];
}
clickNoteButton() {
return [
{
content: 'click note button',
trigger: '.control-buttons .control-button span:contains("Internal Note")',
},
];
}
clickPrintBillButton() {
return [
{
content: 'click print bill button',
trigger: '.control-buttons .control-button.order-printbill',
},
];
}
clickSubmitButton() {
return [
{
content: 'click print bill button',
trigger: '.control-buttons .control-button span:contains("Order")',
},
];
}
clickGuestButton() {
return [
{
content: 'click guest button',
trigger: '.control-buttons .control-button span:contains("Guests")'
}
]
}
}
class CheckExt extends Check {
orderlineHasNote(name, quantity, note) {
return [
{
content: `line has ${quantity} quantity`,
trigger: `.order .orderline .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
run: function () {}, // it's a check
},
{
content: `line has '${note}' note`,
trigger: `.order .orderline .info-list .orderline-note:contains("${note}")`,
run: function () {}, // it's a check
},
];
}
guestNumberIs(numberInString) {
return [
{
content: `guest number is ${numberInString}`,
trigger: `.control-buttons .control-button span.control-button-number:contains(${numberInString})`,
run: function () {}, // it's a check
}
]
}
}
class ExecuteExt extends Execute {}
return createTourMethods('ProductScreen', DoExt, CheckExt, ExecuteExt);
});

View file

@ -1,65 +0,0 @@
odoo.define('pos_restaurant.tour.SplitBillScreenTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
class Do {
clickOrderline(name, totalQuantity) {
let trigger = `li.orderline .product-name:contains("${name}")`;
if (totalQuantity) {
trigger += ` ~ .info-list .info:contains("${totalQuantity}")`;
}
return [
{
content: `click '${name}' orderline with total quantity of '${totalQuantity}'`,
trigger,
},
];
}
clickBack() {
return [
{
content: 'click back button',
trigger: `.splitbill-screen .button.back`,
},
];
}
clickPay() {
return [
{
content: 'click pay button',
trigger: `.splitbill-screen .pay-button .button`
}
]
}
}
class Check {
orderlineHas(name, totalQuantity, splitQuantity) {
return [
{
content: `'${name}' orderline has total quantity of '${totalQuantity}'`,
trigger: `li.orderline .product-name:contains("${name}") ~ .info-list .info:contains("${totalQuantity}")`,
run: () => {},
},
{
content: `'${name}' orderline has '${splitQuantity}' quantity to split`,
trigger: `li.orderline .product-name:contains("${name}") ~ .info-list .info em:contains("${splitQuantity}")`,
run: () => {},
},
];
}
subtotalIs(amount) {
return [
{
content: `total amount of split is '${amount}'`,
trigger: `.splitbill-screen .order-info .subtotal:contains("${amount}")`,
},
];
}
}
class Execute {}
return createTourMethods('SplitBillScreen', Do, Check, Execute);
});

View file

@ -1,60 +0,0 @@
odoo.define('pos_restaurant.tour.TipScreenTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
class Do {
clickPercentTip(percent) {
return [
{
trigger: `.tip-screen .percentage:contains("${percent}")`,
},
];
}
setCustomTip(amount) {
return [
{
trigger: `.tip-screen .custom-amount-form input`,
run: `text ${amount}`,
},
];
}
}
class Check {
isShown() {
return [
{
trigger: '.pos .tip-screen',
run: () => {},
},
];
}
totalAmountIs(amount) {
return [
{
trigger: `.tip-screen .total-amount:contains("${amount}")`,
run: () => {},
},
];
}
percentAmountIs(percent, amount) {
return [
{
trigger: `.tip-screen .percentage:contains("${percent}") ~ .amount:contains("${amount}")`,
run: () => {},
},
];
}
inputAmountIs(amount) {
return [
{
trigger: `.tip-screen .custom-amount-form input[data-amount="${amount}"]`,
run: () => {},
}
]
}
}
return createTourMethods('TipScreen', Do, Check);
});

View file

@ -1,126 +0,0 @@
odoo.define('pos_reataurant.tour.synchronized_table_management', function (require) {
'use strict';
const { BillScreen } = require('pos_restaurant.tour.BillScreenTourMethods');
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
const Tour = require('web_tour.tour');
startSteps();
FloorScreen.do.clickTable('T5');
// Create first order
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
ProductScreen.check.selectedOrderlineHas('Coca-Cola');
ProductScreen.do.clickDisplayedProduct('Water');
ProductScreen.check.selectedOrderlineHas('Water');
ProductScreen.check.totalAmountIs('4.40');
// Create 2nd order (paid)
Chrome.do.clickTicketButton();
TicketScreen.do.clickNewTicket();
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
ProductScreen.check.selectedOrderlineHas('Coca-Cola');
ProductScreen.do.clickDisplayedProduct('Minute Maid');
ProductScreen.check.selectedOrderlineHas('Minute Maid');
ProductScreen.check.totalAmountIs('4.40');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.do.clickValidate();
ReceiptScreen.do.clickNextOrder();
// After clicking next order, floor screen is shown.
// It should have 1 as number of draft synced order.
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
FloorScreen.do.clickTable('T5');
ProductScreen.check.totalAmountIs('4.40');
// Create another draft order and go back to floor
Chrome.do.clickTicketButton();
TicketScreen.do.clickNewTicket();
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
ProductScreen.check.selectedOrderlineHas('Coca-Cola');
ProductScreen.do.clickDisplayedProduct('Minute Maid');
ProductScreen.check.selectedOrderlineHas('Minute Maid');
Chrome.do.backToFloor();
// At floor screen, there should be 2 synced draft orders
FloorScreen.check.orderCountSyncedInTableIs('T5', '2');
// Delete the first order then go back to floor
FloorScreen.do.clickTable('T5');
ProductScreen.check.isShown();
Chrome.do.clickTicketButton();
TicketScreen.do.deleteOrder('-0001');
Chrome.do.confirmPopup();
TicketScreen.do.selectOrder('-0003');
Chrome.do.backToFloor();
// There should be 1 synced draft order.
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
Tour.register('pos_restaurant_sync', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
/* pos_restaurant_sync_second_login
*
* This tour should be run after the first tour is done.
*/
// There is one draft synced order from the previous tour
FloorScreen.check.orderCountSyncedInTableIs('T5', '1');
FloorScreen.do.clickTable('T5');
ProductScreen.check.totalAmountIs('4.40');
// Test transfering an order
ProductScreen.do.clickTransferButton();
FloorScreen.do.clickTable('T4');
// Test if products still get merged after transfering the order
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
ProductScreen.check.selectedOrderlineHas('Coca-Cola', '2.0');
ProductScreen.check.totalAmountIs('6.60');
ProductScreen.do.pressNumpad('1');
ProductScreen.check.totalAmountIs('4.40');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.do.clickValidate();
ReceiptScreen.do.clickNextOrder();
// At this point, there are no draft orders.
FloorScreen.do.clickTable('T2');
ProductScreen.check.isShown();
ProductScreen.check.orderIsEmpty();
ProductScreen.do.clickTransferButton();
FloorScreen.do.clickTable('T4');
ProductScreen.do.clickDisplayedProduct('Coca-Cola');
ProductScreen.check.totalAmountIs('2.20');
Chrome.do.backToFloor();
FloorScreen.check.orderCountSyncedInTableIs('T4', '1');
Tour.register('pos_restaurant_sync_second_login', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
FloorScreen.do.clickTable("5");
ProductScreen.do.clickDisplayedProduct("Coca-Cola");
BillScreen.do.clickBillButton();
BillScreen.check.isShown();
BillScreen.check.isQRCodeNotShown();
BillScreen.do.clickOk();
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod("Bank");
PaymentScreen.do.clickValidate();
BillScreen.check.isQRCodeShown();
Tour.register('BillScreenTour', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -1,45 +0,0 @@
odoo.define('pos_restaurant.tour.Refund', function (require) {
'use strict';
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');
const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
var Tour = require('web_tour.tour');
// signal to start generating steps
// when finished, steps can be taken from getSteps
startSteps();
// Create first order and pay it
FloorScreen.do.clickTable("T2");
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickDisplayedProduct("Coca-Cola");
ProductScreen.check.selectedOrderlineHas("Coca-Cola");
ProductScreen.do.clickDisplayedProduct("Coca-Cola");
ProductScreen.check.selectedOrderlineHas("Coca-Cola");
ProductScreen.do.clickDisplayedProduct("Water");
ProductScreen.check.selectedOrderlineHas("Water");
ProductScreen.check.totalAmountIs("6.60");
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod("Cash");
PaymentScreen.do.clickValidate();
ReceiptScreen.do.clickNextOrder();
// Go to another table and refund one of the product
FloorScreen.do.clickTable("T4");
ProductScreen.check.orderIsEmpty();
ProductScreen.do.clickRefund();
TicketScreen.do.selectOrder("-0001");
TicketScreen.do.clickOrderline("Coca-Cola");
TicketScreen.do.pressNumpad("2");
TicketScreen.check.toRefundTextContains("To Refund: 2.00");
TicketScreen.do.confirmRefund();
ProductScreen.check.isShown();
ProductScreen.check.selectedOrderlineHas("Coca-Cola");
ProductScreen.check.totalAmountIs("-4.40");
Tour.register('RefundStayCurrentTableTour', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -0,0 +1,53 @@
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
import { registry } from "@web/core/registry";
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
registry.category("web_tour.tours").add("RefundStayCurrentTableTour", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
// Create first order and pay it
FloorScreen.clickTable("2"),
ProductScreen.clickDisplayedProduct("Coca-Cola", true, "1"),
ProductScreen.clickDisplayedProduct("Coca-Cola", true, "2"),
ProductScreen.clickDisplayedProduct("Water", true, "1"),
ProductScreen.totalAmountIs("6.60"),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
// Go to another table and refund one of the products
FloorScreen.clickTable("4"),
ProductScreen.orderIsEmpty(),
...ProductScreen.clickRefund(),
TicketScreen.selectOrder("001"),
Order.hasLine({
productName: "Coca-Cola",
}),
ProductScreen.clickNumpad("2"),
TicketScreen.toRefundTextContains("To Refund: 2"),
TicketScreen.confirmRefund(),
PaymentScreen.isShown(),
PaymentScreen.clickBack(),
ProductScreen.isShown(),
inLeftSide(ProductScreen.orderLineHas("Coca-Cola")),
ProductScreen.totalAmountIs("-4.40"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});

View file

@ -0,0 +1,344 @@
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
import * as ChromePos from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import * as ChromeRestaurant from "@pos_restaurant/../tests/tours/utils/chrome";
const Chrome = { ...ChromePos, ...ChromeRestaurant };
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
import * as SplitBillScreen from "@pos_restaurant/../tests/tours/utils/split_bill_screen_util";
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
import * as combo from "@point_of_sale/../tests/pos/tours/utils/combo_popup_util";
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add("SplitBillScreenTour", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Water", "5", "2", "10.0"),
ProductScreen.addOrderline("Minute Maid", "3", "2", "6.0"),
ProductScreen.addOrderline("Coca-Cola", "1", "2", "2.0"),
ProductScreen.clickControlButton("Split"),
// Check if the screen contains all the orderlines
SplitBillScreen.orderlineHas("Water", "5", "0"),
SplitBillScreen.orderlineHas("Minute Maid", "3", "0"),
SplitBillScreen.orderlineHas("Coca-Cola", "1", "0"),
// split 3 water and 1 coca-cola
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.orderlineHas("Water", "5", "1"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.orderlineHas("Water", "5", "3"),
SplitBillScreen.subtotalIs("6.0"),
SplitBillScreen.clickOrderline("Coca-Cola"),
SplitBillScreen.orderlineHas("Coca-Cola", "1", "1"),
SplitBillScreen.subtotalIs("8.0"),
// click pay to split, go back to check the lines
SplitBillScreen.clickButton("Split"),
ProductScreen.totalAmountIs("8.0"),
ProductScreen.clickOrderline("Water", "3"),
ProductScreen.clickOrderline("Coca-Cola", "1"),
// go back to the original order and see if the order is changed
Chrome.clickOrders(),
TicketScreen.selectOrder("001"),
TicketScreen.loadSelectedOrder(),
ProductScreen.isShown(),
ProductScreen.clickOrderline("Water", "2"),
ProductScreen.clickOrderline("Minute Maid", "3"),
// Split the order of table 2 again
Chrome.clickPlanButton(),
FloorScreen.clickTable("2"),
ProductScreen.clickControlButton("Split"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.orderlineHas("Water", "2", "1"),
SplitBillScreen.subtotalIs("2.0"),
SplitBillScreen.clickOrderline("Minute Maid"),
SplitBillScreen.orderlineHas("Minute Maid", "3", "1"),
SplitBillScreen.subtotalIs("4.0"),
SplitBillScreen.clickButton("Split"),
ProductScreen.totalAmountIs("4.0"),
// go back to the original order and see if the order is changed
Chrome.clickOrders(),
TicketScreen.selectOrder("001"),
TicketScreen.loadSelectedOrder(),
ProductScreen.isShown(),
ProductScreen.clickOrderline("Water", "1"),
ProductScreen.clickOrderline("Minute Maid", "2"),
].flat(),
});
registry.category("web_tour.tours").add("SplitBillScreenTourPay", {
steps: () =>
[
Chrome.startPoS(),
//Split pay by selecting products
FloorScreen.clickTable("4"),
ProductScreen.addOrderline("Water"),
ProductScreen.addOrderline("Minute Maid"),
ProductScreen.clickControlButton("Split"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.clickButton("Pay"),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickContinueOrder(),
SplitBillScreen.clickOrderline("Minute Maid"),
SplitBillScreen.clickOrderline("Minute Maid"),
SplitBillScreen.clickButton("Pay"),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
].flat(),
});
registry.category("web_tour.tours").add("SplitBillScreenTour2", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Water", "1", "2.0"),
ProductScreen.addOrderline("Minute Maid", "1", "2.0"),
ProductScreen.addOrderline("Coca-Cola", "1", "2.0"),
Chrome.clickPlanButton(),
FloorScreen.clickTable("2"),
Chrome.isSynced(),
ProductScreen.clickControlButton("Split"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.orderlineHas("Water", "1", "1"),
SplitBillScreen.clickOrderline("Coca-Cola"),
SplitBillScreen.orderlineHas("Coca-Cola", "1", "1"),
SplitBillScreen.clickButton("Split"),
ProductScreen.totalAmountIs("4.0"),
Chrome.clickOrders(),
TicketScreen.selectOrder("2B"),
TicketScreen.loadSelectedOrder(),
Order.hasLine({ productName: "Coca-Cola", quantity: "1" }),
Order.hasLine({ productName: "Water", quantity: "1" }),
ProductScreen.totalAmountIs("4.00"),
Chrome.clickOrders(),
TicketScreen.selectOrder("001"),
TicketScreen.loadSelectedOrder(),
Order.hasLine({ productName: "Minute Maid", quantity: "1" }),
ProductScreen.totalAmountIs("2.00"),
].flat(),
});
registry.category("web_tour.tours").add("SplitBillScreenTour3", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Water", "2", "2", "4.00"),
ProductScreen.clickControlButton("Split"),
// Check if the screen contains all the orderlines
SplitBillScreen.orderlineHas("Water", "2", "0"),
// split 1 water
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.orderlineHas("Water", "2", "1"),
SplitBillScreen.subtotalIs("2.0"),
// click pay to split, and pay
SplitBillScreen.clickButton("Split"),
ProductScreen.totalAmountIs("2.0"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickContinueOrder(),
// Check if there is still water in the order
ProductScreen.isShown(),
ProductScreen.orderLineHas("Water", "1.0"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
// Check if there is no more order to continue
ReceiptScreen.clickNextOrder(),
].flat(),
});
registry.category("web_tour.tours").add("SplitBillScreenTour4ProductCombo", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.clickDisplayedProduct("Office Combo"),
combo.select("Combo Product 3"),
combo.select("Combo Product 5"),
combo.select("Combo Product 8"),
Dialog.confirm(),
...ProductScreen.clickDisplayedProduct("Office Combo"),
combo.select("Combo Product 2"),
combo.select("Combo Product 4"),
combo.select("Combo Product 7"),
Dialog.confirm(),
// now we set the qty of this combo combination to 2
ProductScreen.clickOrderline("Combo Product 2"),
ProductScreen.clickNumpad("2"),
ProductScreen.selectedOrderlineHas("Combo Product 2", "2"),
ProductScreen.addOrderline("Water", "1"),
ProductScreen.addOrderline("Minute Maid", "1"),
// The water, the first combo, and one out of the two items
// of the second combo will go in the new splitted order.
// we will then check if the rest of the items from the selected
// combos are automatically sent to the new order.
ProductScreen.clickControlButton("Split"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.clickOrderline("Combo Product 3"),
SplitBillScreen.clickOrderline("Combo Product 2"),
// we check that all the lines in the combo are splitted together
SplitBillScreen.orderlineHas("Water", "1", "1"),
SplitBillScreen.orderlineHas("Office Combo", "1", "1"),
SplitBillScreen.orderlineHas("Combo Product 3", "1", "1"),
SplitBillScreen.orderlineHas("Combo Product 5", "1", "1"),
SplitBillScreen.orderlineHas("Combo Product 8", "1", "1"),
SplitBillScreen.orderlineHas("Office Combo", "2", "1"),
SplitBillScreen.orderlineHas("Combo Product 2", "2", "0"),
SplitBillScreen.orderlineHas("Combo Product 4", "2", "0"),
SplitBillScreen.orderlineHas("Combo Product 7", "2", "0"),
...SplitBillScreen.subtotalIs("97.15"),
...SplitBillScreen.clickButton("Split"),
ProductScreen.totalAmountIs("97.13"),
ProductScreen.clickPayButton(),
...PaymentScreen.clickPaymentMethod("Bank"),
...PaymentScreen.clickValidate(),
...ReceiptScreen.clickContinueOrder(),
// Check if there is still water in the order
...ProductScreen.isShown(),
// now we check that all the lines that remained in the order are correct
...ProductScreen.orderLineHas("Minute Maid", "1"),
...ProductScreen.clickOrderline("Office Combo"),
...ProductScreen.selectedOrderlineHas("Office Combo", "1", "43.33"),
...ProductScreen.orderLineHas("Combo Product 2", "1"),
...ProductScreen.orderLineHas("Combo Product 4", "1"),
...ProductScreen.orderLineHas("Combo Product 7", "1"),
...ProductScreen.totalAmountIs("45.53"),
].flat(),
});
registry.category("web_tour.tours").add("SplitBillScreenTour5Actions", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Water", "2", "2", "4.00"),
ProductScreen.addOrderline("Minute Maid", "1", "3", "3.00"),
ProductScreen.clickControlButton("Split"),
SplitBillScreen.orderlineHas("Water", "2", "0"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.clickOrderline("Minute Maid"),
SplitBillScreen.subtotalIs("5.0"),
// click transfer button to split and transfer
SplitBillScreen.clickButton("Transfer"),
FloorScreen.isShown(),
FloorScreen.clickTable("5"),
// check table 5 order and pay
ProductScreen.orderLineHas("Water", "1"),
ProductScreen.orderLineHas("Minute Maid", "1"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
// Add products in order
FloorScreen.clickTable("2"),
ProductScreen.orderLineHas("Water", "1"),
ProductScreen.addOrderline("Minute Maid", "2", "3", "6.00"),
ProductScreen.clickControlButton("Split"),
SplitBillScreen.clickOrderline("Minute Maid"),
SplitBillScreen.clickOrderline("water"),
SplitBillScreen.subtotalIs("5.0"),
// click pay to split, and pay
SplitBillScreen.clickButton("Pay"),
PaymentScreen.isShown(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickContinueOrder(),
// Check if redirect to split bill screen of original order
SplitBillScreen.orderlineHas("Minute Maid", "1", "0"),
SplitBillScreen.clickButton("Pay"),
PaymentScreen.isShown(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
].flat(),
});
registry.category("web_tour.tours").add("SplitBillScreenTourTransfer", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Water", "5", "2", "10.0"),
ProductScreen.addOrderline("Minute Maid", "3", "2", "6.0"),
ProductScreen.addOrderline("Coca-Cola", "1", "2", "2.0"),
ProductScreen.clickControlButton("Discount"),
Dialog.confirm(),
ProductScreen.selectedOrderlineHas("discount", 1, "-1.80"),
ProductScreen.clickControlButton("Split"),
// Check if the screen contains all the orderlines
SplitBillScreen.orderlineHas("Water", "5", "0"),
SplitBillScreen.orderlineHas("Minute Maid", "3", "0"),
SplitBillScreen.orderlineHas("Coca-Cola", "1", "0"),
Order.doesNotHaveLine({ productName: "Discount" }),
// split 3 water and 1 coca-cola
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.orderlineHas("Water", "5", "1"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.clickOrderline("Water"),
SplitBillScreen.orderlineHas("Water", "5", "3"),
SplitBillScreen.subtotalIs("6.0"),
SplitBillScreen.clickOrderline("Coca-Cola"),
SplitBillScreen.orderlineHas("Coca-Cola", "1", "1"),
SplitBillScreen.subtotalIs("8.0"),
// click pay to split, go back to check the lines
SplitBillScreen.clickButton("Transfer"),
FloorScreen.clickTable("5"),
Order.doesNotHaveLine({ productName: "Discount" }),
ProductScreen.totalAmountIs("8.0"),
ProductScreen.clickOrderline("Water", "3"),
ProductScreen.clickOrderline("Coca-Cola", "1"),
// go back to the original order and see if the order is changed
Chrome.clickOrders(),
TicketScreen.selectOrder("001"),
TicketScreen.loadSelectedOrder(),
ProductScreen.isShown(),
ProductScreen.clickOrderline("Water", "2"),
ProductScreen.clickOrderline("Minute Maid", "3"),
].flat(),
});

View file

@ -0,0 +1,97 @@
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import { registry } from "@web/core/registry";
const { DateTime } = luxon;
registry.category("web_tour.tours").add("PosResTicketScreenTour", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
// New Ticket button should not be in the ticket screen if no table is selected.
Chrome.clickOrders(),
Chrome.clickPlanButton(),
// Make sure that order is deleted properly.
FloorScreen.clickTable("5"),
ProductScreen.addOrderline("Minute Maid", "1", "3"),
ProductScreen.totalAmountIs("3.0"),
Chrome.clickPlanButton(),
FloorScreen.orderCountSyncedInTableIs("5", 0),
Chrome.clickOrders(),
TicketScreen.deleteOrder("001"),
Dialog.confirm(),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
FloorScreen.clickTable("5"),
ProductScreen.orderIsEmpty(),
].flat(),
});
registry.category("web_tour.tours").add("test_cancel_order_from_ui", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("5"),
ProductScreen.isShown(),
ProductScreen.addOrderline("Coca-Cola", "1", "3"),
Chrome.clickPlanButton(),
Chrome.isSynced(),
FloorScreen.isShown(),
FloorScreen.clickTable("5"),
ProductScreen.clickReview(),
ProductScreen.clickControlButton("Cancel Order"),
Dialog.confirm(),
FloorScreen.isShown(),
Chrome.clickOrders(),
TicketScreen.noOrderIsThere(),
TicketScreen.selectFilter("Paid"),
TicketScreen.noOrderIsThere(),
Chrome.storedOrderCount(0),
].flat(),
});
registry.category("web_tour.tours").add("OrderNumberConflictTour", {
steps: () =>
[
Chrome.startPoS(),
FloorScreen.clickTable("3"),
ProductScreen.isShown(),
ProductScreen.addOrderline("Coca-Cola", "1", "3"),
Chrome.clickPlanButton(),
Chrome.clickOrders(),
TicketScreen.nthRowContains(1, `${String(DateTime.now().year).slice(-2)}0`),
TicketScreen.nthRowContains(1, "T 101"),
TicketScreen.nthRowContains(2, `${String(DateTime.now().year).slice(-2)}1`),
TicketScreen.nthRowContains(2, "T 103"),
].flat(),
});
registry.category("web_tour.tours").add("test_sync_lines_qty_update_ticket_screen", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
Chrome.clickRegister(),
ProductScreen.addOrderline("Coca-Cola", "1"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A powerful Pos man!"),
Chrome.clickOrders(),
TicketScreen.selectOrder("001"),
TicketScreen.loadSelectedOrder(),
ProductScreen.clickOrderline("Coca-Cola", "1"),
ProductScreen.clickNumpad("3"),
ProductScreen.selectedOrderlineHas("Coca-Cola", "3"),
Chrome.clickOrders(),
].flat(),
});

View file

@ -0,0 +1,210 @@
import * as ProductScreenPos from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as ProductScreenResto from "@pos_restaurant/../tests/tours/utils/product_screen_util";
const ProductScreen = { ...ProductScreenPos, ...ProductScreenResto };
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
import * as FloorScreen from "@pos_restaurant/../tests/tours/utils/floor_screen_util";
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
import * as TipScreen from "@pos_restaurant/../tests/tours/utils/tip_screen_util";
import * as NumberPopup from "@point_of_sale/../tests/generic_helpers/number_popup_util";
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add("PosResTipScreenTour", {
steps: () =>
[
// Create order that is synced when draft.
// order 1
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Minute Maid", "1", "2"),
ProductScreen.totalAmountIs("2.0"),
Chrome.clickPlanButton(),
FloorScreen.orderCountSyncedInTableIs("2", "1"),
FloorScreen.clickTable("2"),
ProductScreen.totalAmountIs("2.0"),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
TipScreen.isShown(),
Chrome.clickPlanButton(),
FloorScreen.clickTable("4"),
// order 2
ProductScreen.addOrderline("Coca-Cola", "2", "2"),
ProductScreen.totalAmountIs("4.0"),
Chrome.clickPlanButton(),
Chrome.clickOrders(),
TicketScreen.selectFilter("Active"),
{
trigger: `.ticket-screen .orders .order-row:contains(Tipping):contains($ 2.00)`,
},
Chrome.clickPlanButton(),
// Create without syncing the draft.
// order 3
FloorScreen.clickTable("5"),
ProductScreen.addOrderline("Minute Maid", "3", "2"),
ProductScreen.totalAmountIs("6.0"),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
TipScreen.isShown(),
Chrome.clickPlanButton(),
FloorScreen.clickNewOrder(),
// order 4
ProductScreen.addOrderline("Coca-Cola", "4", "2"),
ProductScreen.totalAmountIs("8.0"),
ProductScreen.clickControlButton("Guests"),
NumberPopup.enterValue("2"),
NumberPopup.isShown("2"),
Dialog.confirm(),
ProductScreen.guestNumberIs("2"),
ProductScreen.clickCloseButton(),
ProductScreen.setTab("Test"),
Chrome.clickOrders(),
TicketScreen.selectFilter("Active"),
{
trigger: `.ticket-screen .orders .order-row:contains(Tipping):contains($ 6.00)`,
},
// Tip 20% on order1
TicketScreen.selectOrderByPrice("2.0"),
TicketScreen.loadSelectedOrder(),
TipScreen.isShown(),
TipScreen.totalAmountIs("2.0"),
TipScreen.percentAmountIs("15%", "0.30"),
TipScreen.percentAmountIs("20%", "0.40"),
TipScreen.percentAmountIs("25%", "0.50"),
TipScreen.clickPercentTip("20%"),
TipScreen.inputAmountIs("0.40"),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
Chrome.clickOrders(),
TicketScreen.selectFilter("Active"),
// Tip 25% on order3
TicketScreen.selectOrderByPrice("6.0"),
TicketScreen.loadSelectedOrder(),
TipScreen.isShown(),
TipScreen.totalAmountIs("6.0"),
TipScreen.percentAmountIs("15%", "0.90"),
TipScreen.percentAmountIs("20%", "1.20"),
TipScreen.percentAmountIs("25%", "1.50"),
TipScreen.clickPercentTip("25%"),
TipScreen.inputAmountIs("1.50"),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
Chrome.clickOrders(),
// finalize order 4 then tip custom amount
TicketScreen.selectOrderByPrice("8.0"),
TicketScreen.loadSelectedOrder(),
ProductScreen.isShown(),
ProductScreen.totalAmountIs("8.0"),
ProductScreen.guestNumberIs("2"),
ProductScreen.clickCloseButton(),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
TipScreen.isShown(),
TipScreen.totalAmountIs("8.0"),
TipScreen.percentAmountIs("15%", "1.20"),
TipScreen.percentAmountIs("20%", "1.60"),
TipScreen.percentAmountIs("25%", "2.00"),
TipScreen.setCustomTip("1.00"),
TipScreen.inputAmountIs("1.00"),
Chrome.clickPlanButton(),
FloorScreen.isShown(),
// settle tips here
Chrome.clickOrders(),
TicketScreen.selectFilter("Tipping"),
TicketScreen.tipContains("1.00"),
TicketScreen.settleTips(),
TicketScreen.selectFilter("Active"),
{
trigger: `.ticket-screen .orders .order-row:contains(Ongoing):contains($ 4.00)`,
},
// tip order2 during payment
// tip screen should not show after validating payment screen
TicketScreen.selectOrderByPrice("4.0"),
TicketScreen.loadSelectedOrder(),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickTipButton(),
NumberPopup.enterValue("1"),
NumberPopup.isShown("1"),
Dialog.confirm(),
PaymentScreen.emptyPaymentlines("5.0"),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
// order 5
// Click directly on "settle" without selecting a Tip
ReceiptScreen.clickNextOrder(),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Minute Maid", "3", "2"),
ProductScreen.totalAmountIs("6.0"),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
TipScreen.isShown(),
TipScreen.clickSettle(),
ReceiptScreen.isShown(),
ReceiptScreen.clickNextOrder(),
FloorScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("test_tip_after_payment", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
FloorScreen.clickTable("2"),
ProductScreen.addOrderline("Minute Maid", "1", "3"),
ProductScreen.clickPayButton(false),
ProductScreen.discardOrderWarningDialog(),
// case 1: remaining < 0 => increase PaymentLine amount
PaymentScreen.enterPaymentLineAmount("Bank", "1"),
PaymentScreen.clickTipButton(),
{
content: "click numpad button: 1",
trigger: ".modal div.numpad button:contains(/^1/)",
run: "click",
},
Dialog.confirm(),
PaymentScreen.selectedPaymentlineHas("Bank", "2.00"),
// case 2: remaining >= 0 and remaining >= tip => don't change PaymentLine amount
PaymentScreen.clickPaymentlineDelButton("Bank", "2.00"),
PaymentScreen.enterPaymentLineAmount("Bank", "5"),
PaymentScreen.clickTipButton(),
{
content: "click numpad button: 2",
trigger: ".modal div.numpad button:contains(/^2/)",
run: "click",
},
Dialog.confirm(),
PaymentScreen.selectedPaymentlineHas("Bank", "5.00"),
// case 3: remaining >= 0 and remaining < tip => increase by the difference
PaymentScreen.clickPaymentlineDelButton("Bank", "5.00"),
PaymentScreen.enterPaymentLineAmount("Bank", "5"),
PaymentScreen.clickTipButton(),
{
content: "click numpad button: 3",
trigger: ".modal div.numpad button:contains(/^3/)",
run: "click",
},
Dialog.confirm(),
PaymentScreen.selectedPaymentlineHas("Bank", "6.00"),
Chrome.endTour(),
].flat(),
});

View file

@ -0,0 +1,19 @@
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
export function isTabActive(tabText) {
return [
{
content: "Check if the active tab contains the text" + tabText,
trigger: `.pos-leftheader span.text-bg-info:contains(${tabText})`,
},
];
}
export function closePrintingWarning() {
return [
{
...Dialog.confirm(),
content: "acknowledge printing error ( because we don't have printer in the test. )",
},
];
}

View file

@ -0,0 +1,175 @@
/* global posmodel */
const getData = ({ lineProductName, productName, partnerName } = {}) => {
const order = posmodel.models["pos.order"].find((o) => o.pos_reference.includes("device_sync"));
let partner = null;
if (partnerName) {
partner = posmodel.models["res.partner"].find((p) => p.name === partnerName);
}
let line = null;
if (lineProductName) {
line = order.lines.find((l) => l.product_id.display_name === lineProductName);
}
let product = null;
if (productName) {
product = posmodel.models["product.product"].find((p) => p.display_name === productName);
}
return { order, line, product, partner };
};
const notify = async () => {
const orm = posmodel.env.services.orm;
await orm.call("pos.config", "notify_synchronisation", [
posmodel.config.id,
posmodel.session.id,
999,
]);
};
const getLineData = (product, order, quantity) => ({
name: product.display_name,
order_id: order.id,
product_id: product.id,
price_unit: product.lst_price,
price_subtotal: product.lst_price * quantity,
price_subtotal_incl: product.lst_price * quantity,
discount: 0,
qty: quantity,
});
// In the point-of-sale code, we consider that synchronization is necessary
// when the write_date of the local order is smaller than that of the server.
// To prevent the PoS from ignoring our synchronization.
const writeOnOrder = async (order, data) => {
const sec = new Date(order.write_date).getMilliseconds() + 1010;
const timeout = Math.ceil(sec - new Date().getMilliseconds(), 0);
await new Promise((res) => setTimeout(res, timeout));
const orm = posmodel.env.services.orm;
await orm.write("pos.order", [order.id], data);
await notify();
};
/**
* @param {string} productName
* @param {number} quantity
* @returns StepSchema[]
*/
export function createNewLine(productName, quantity) {
return [
{
trigger: "body",
run: async () => {
const { order, product } = getData({ productName });
await writeOnOrder(order, {
lines: [[0, 0, getLineData(product, order, quantity)]],
});
},
},
];
}
/**
* @param {string} productName
* @param {number} quantity
* @returns StepSchema[]
*/
export function changeLineQuantity(productName, quantity) {
return [
{
trigger: "body",
run: async () => {
const { order, line } = getData({ lineProductName: productName });
await writeOnOrder(order, {
lines: [[1, line.id, getLineData(line.product_id, order, quantity)]],
});
},
},
];
}
/**
* @param {string} partnerName
* @returns StepSchema[]
*/
export function changePartner(partnerName) {
return [
{
trigger: "body",
run: async () => {
const { order, partner } = getData({ partnerName });
await writeOnOrder(order, {
partner_id: partner.id,
});
},
},
];
}
export function markOrderAsPaid() {
return [
{
trigger: "body",
run: async () => {
const { order } = getData({});
await writeOnOrder(order, {
state: "paid",
amount_paid: order.amount_total,
amount_return: 0,
amount_tax: 0,
amount_total: 0,
});
},
},
];
}
export function createNewOrderOnTable(tableName, productTuple) {
return [
{
trigger: "body",
run: async () => {
const orm = posmodel.env.services.orm;
const prices = {
amount_paid: 0,
amount_return: 0,
amount_tax: 0,
amount_total: 0,
};
const lines = productTuple.map(([productName, quantity]) => {
const product = posmodel.models["product.product"].find(
(p) => p.display_name === productName
);
const lineData = getLineData(product, false, quantity);
prices.amount_paid += lineData.price_subtotal;
prices.amount_return += lineData.price_subtotal;
return [
0,
0,
{
...lineData,
price_subtotal: lineData.price_subtotal,
price_subtotal_incl: lineData.price_subtotal_incl,
},
];
});
const table = posmodel.models["restaurant.table"].find(
(t) => t.table_number === parseInt(tableName)
);
await orm.create("pos.order", [
{
...prices,
pos_reference: `device_sync_${Math.floor(Math.random() * 9999)}`,
session_id: posmodel.session.id,
table_id: table.id,
lines,
},
]);
await notify();
},
},
];
}

View file

@ -0,0 +1,220 @@
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
export function table({ name, withClass = "", withoutClass, run = () => {}, numOfSeats }) {
let trigger = `.floor-map .table${withClass}`;
if (withoutClass) {
trigger += `:not(${withoutClass})`;
}
if (name) {
trigger += `:has(.label:contains("${name}"))`;
}
return {
content: `Check table with attributes: ${JSON.stringify(arguments[0])}`,
trigger,
run: typeof run === "string" ? run : (helpers) => run(helpers, trigger),
};
}
export const clickTable = (name) => table({ name, run: "click" });
export const hasTable = (name) => table({ name });
export const selectedTableIs = (name) => table({ name, withClass: ".selected" });
export const ctrlClickTable = (name) =>
table({
name,
run: (helpers, trigger) => {
helpers
.queryOne(trigger)
.dispatchEvent(new MouseEvent("click", { bubbles: true, ctrlKey: true }));
},
});
export function clickFloor(name) {
return [
{
content: `click '${name}' floor`,
trigger: `.floor-selector .button-floor:contains("${name}")`,
run: "click",
},
];
}
export function hasFloor(name) {
return [
{
content: `has '${name}' floor`,
trigger: `.floor-selector .button-floor:contains("${name}")`,
},
];
}
export function hasNotFloor(name) {
return [
{
content: `has not '${name}' floor`,
trigger: negate(`.floor-selector .button-floor:contains("${name}")`),
},
];
}
export function clickEditButton(button) {
return [
{
content: "add table",
trigger: `.edit-buttons i[aria-label="${button}"]`,
run: "click",
},
];
}
export function clickSaveEditButton() {
return [
{
content: "add table",
trigger: '.edit-buttons button:contains("Save")',
run: "click",
},
{
trigger: negate(".edit-buttons button:contains('Save')"),
},
];
}
export function clickTableSelectorButton() {
return [
{
content: "click on table selector button",
trigger: ".floor-screen .right-buttons button i.fa-hashtag",
run: "click",
},
];
}
export function goTo(name) {
return [
...clickTableSelectorButton(),
...Numpad.enterValue(name),
{
trigger: ".floor-screen .right-buttons .jump-button",
run: "click",
},
];
}
export function selectedFloorIs(name) {
return [
{
content: `selected floor is '${name}'`,
trigger: `.button-floor.active:contains("${name}")`,
},
];
}
export function orderCountSyncedInTableIs(table, count) {
if (count === 0 || count === "0") {
return [
{
trigger: `.floor-map .table:has(.label:contains("${table}")):not(:has(.order-count))`,
},
];
}
return [
{
trigger: `.floor-map .table:has(.label:contains("${table}")):has(.order-count:contains("${count}"))`,
},
];
}
export function isShown() {
return [
{
trigger: ".floor-map",
},
];
}
export function linkTables(child, parent) {
async function drag_multiple_and_then_drop(helpers, ...drags) {
const dragEffectDelay = async () => {
console.log(helpers.delay);
await new Promise((resolve) => requestAnimationFrame(resolve));
await new Promise((resolve) => setTimeout(resolve, helpers.delay));
};
const element = helpers.anchor;
const { drag } = odoo.loader.modules.get("@odoo/hoot-dom");
const { drop, moveTo } = await drag(element);
await dragEffectDelay();
await helpers.hover(element, {
position: {
top: 20,
left: 20,
},
relative: true,
});
await dragEffectDelay();
for (const [selector, options] of drags) {
console.log("Selector", selector, options);
const target = await helpers.waitFor(selector, {
visible: true,
timeout: 500,
});
await moveTo(target, options);
await dragEffectDelay();
}
await drop();
await dragEffectDelay();
}
return {
content: `Drag table ${child} onto table ${parent} in order to link them`,
trigger: table({ name: child }).trigger,
async run(helpers) {
helpers.delay = 500;
await drag_multiple_and_then_drop(
helpers,
[
table({ name: parent }).trigger,
{
position: "top",
relative: true,
},
],
[
table({ name: parent }).trigger,
{
position: "center",
relative: true,
},
]
);
},
};
}
export function unlinkTables(child, parent) {
return {
content: `Drag table ${child} away from table ${parent} to unlink them`,
trigger: table({ name: child }).trigger,
async run(helpers) {
await helpers.drag_and_drop(`div.floor-map`, {
position: {
bottom: 0,
},
relative: true,
});
},
};
}
export function isChildTable(child) {
return {
content: `Verify that table ${child} is a child table`,
trigger: table({ name: child }).trigger + ` .info.opacity-25`,
};
}
export function clickNewOrder() {
return { trigger: ".new-order", run: "click" };
}
export function addFloor(floorName) {
return [
{
trigger: ".floor-selector button i[aria-label='Add Floor']",
run: "click",
},
{
trigger: ".modal-body textarea",
run: `edit ${floorName}`,
},
{
trigger: ".modal-footer button.btn-primary",
run: "click",
},
...selectedFloorIs(floorName),
];
}

View file

@ -0,0 +1,145 @@
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as TextInputPopup from "@point_of_sale/../tests/generic_helpers/text_input_popup_util";
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
export function clickOrderButton() {
return [
{
content: "click order button",
trigger: ".actionpad .submit-order",
run: "click",
},
];
}
export function orderlinesHaveNoChange() {
return Order.doesNotHaveLine({ withClass: ".has-change" });
}
export function orderlineIsToOrder(name) {
return Order.hasLine({
productName: name,
withClass: ".orderline.has-change",
});
}
export function guestNumberIs(num) {
return [
...ProductScreen.clickControlButtonMore(),
{
content: `guest number is ${num}`,
trigger: ProductScreen.controlButtonTrigger("Guests") + `:contains(${num})`,
},
];
}
export function OrderButtonNotContain(data) {
const steps = [
{
isActive: ["desktop"],
content: "check order button not contain data",
trigger: `.product-screen .submit-order:not(:contains("${data}"))`,
run: function () {}, // it's a check
},
];
return steps;
}
export function clickCourseButton() {
return [
{
content: "click course button",
trigger: `.course-btn`,
run: "click",
},
];
}
export function selectCourseLine(name) {
return [
{
content: `select course ${name}`,
trigger: `.order-course-name:contains(${name})`,
run: "click",
},
];
}
export function fireCourseButton() {
return [
{
content: "fire course button",
trigger: `.actionpad .fire-btn`,
run: "click",
},
];
}
export function fireCourseButtonHighlighted(courseName) {
return [
{
content: "fire course button highlighted",
trigger: `.actionpad .fire-btn.btn-primary:contains('Fire ${courseName}')`,
},
];
}
export function payButtonNotHighlighted() {
return [
{
content: "pay button not highlighted",
trigger:
".actionpad .pay-order-button:not('.highlight'):not('.btn-primary'):contains('Payment')",
},
];
}
export function setTab(name) {
return [
{
content: `set tab to ${name}`,
trigger: `.product-screen .new-tab`,
run: "click",
},
TextInputPopup.inputText(name),
Dialog.confirm(),
];
}
export function releaseTable() {
return [
{
content: "release table",
trigger: ".product-screen .leftpane .unbook-table",
run: "click",
},
];
}
export function addCourse() {
return {
content: `click Course button`,
trigger: ProductScreen.controlButtonTrigger("Course"),
run: "click",
};
}
export function transferCourseTo(destCourse) {
return [
...ProductScreen.clickControlButton("Transfer course"),
{
content: `click ${destCourse} from available courses`,
trigger: `.modal-body button:contains(${destCourse})`,
run: "click",
},
];
}
export function discardOrderWarningDialog() {
return [
{
trigger: `.modal-dialog:contains("It seems that the order has not been sent. Would you like to send it to preparation?")`,
},
Dialog.discard(),
];
}
export function confirmOrderWarningDialog() {
return [
{
trigger: `.modal-dialog:contains("It seems that the order has not been sent. Would you like to send it to preparation?")`,
},
Dialog.confirm(),
];
}

View file

@ -0,0 +1,39 @@
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
export function clickOrderline(productName) {
return Order.hasLine({ productName, run: "click" });
}
export function clickBack() {
return [
{
content: "click back button",
trigger: `.splitbill-screen .button.back`,
run: "click",
},
];
}
export function clickButton(name) {
return [
{
content: `click '${name}' button`,
trigger: `.splitbill-screen .pay-button button:contains("${name}")`,
run: "click",
},
];
}
export function orderlineHas(name, totalQuantity, splitQuantity) {
return Order.hasLine({
productName: name,
quantity: splitQuantity != 0 ? `${splitQuantity} / ${totalQuantity}` : totalQuantity,
});
}
export function subtotalIs(amount) {
return [
{
content: `total amount of split is '${amount}'`,
trigger: `.splitbill-screen .order-info .subtotal:contains("${amount}")`,
run: "click",
},
];
}

View file

@ -0,0 +1,53 @@
export function clickPercentTip(percent) {
return [
{
trigger: `.tip-screen .percentage:contains("${percent}")`,
run: "click",
},
];
}
export function setCustomTip(amount) {
return [
{
trigger: `.tip-screen .custom-amount-form input`,
run: `edit ${amount}`,
},
];
}
export function clickSettle() {
return [
{
trigger: `.button.highlight.next`,
run: "click",
},
];
}
export function isShown() {
return [
{
trigger: ".pos .tip-screen",
},
];
}
export function totalAmountIs(amount) {
return [
{
trigger: `.tip-screen .total-amount:contains("${amount}")`,
},
];
}
export function percentAmountIs(percent, amount) {
return [
{
trigger: `.tip-screen .percentage:contains("${percent}") ~ .amount:contains("${amount}")`,
},
];
}
export function inputAmountIs(amount) {
return [
{
trigger: `.tip-screen .custom-amount-form input[data-amount="${amount}"]`,
},
];
}

View file

@ -0,0 +1,47 @@
import { expect, test } from "@odoo/hoot";
import { getFilledOrder, setupPosEnv } from "@point_of_sale/../tests/unit/utils";
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
import { ActionpadWidget } from "@point_of_sale/app/screens/product_screen/action_pad/action_pad";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("highlightPay", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const comp = await mountWithCleanup(ActionpadWidget, {
props: {
actionName: "Payment",
actionToTrigger: () => {},
},
});
expect(comp.highlightPay).toBe(false);
// simulating order send
order.updateLastOrderChange();
expect(comp.highlightPay).toBe(true);
// orderline qty change
order.lines[1].qty = 21;
expect(comp.highlightPay).toBe(false);
order.updateLastOrderChange();
expect(comp.highlightPay).toBe(true);
// orderline note update
order.lines[0].note = "Test Orderline Note";
expect(comp.highlightPay).toBe(false);
order.updateLastOrderChange();
expect(comp.highlightPay).toBe(true);
// general customer note
order.general_customer_note = "Test Order Customer Note";
expect(comp.highlightPay).toBe(false);
order.updateLastOrderChange();
expect(comp.highlightPay).toBe(true);
// internal note
order.internal_note = "Test Order Internal Note";
expect(comp.highlightPay).toBe(false);
order.updateLastOrderChange();
expect(comp.highlightPay).toBe(true);
});

View file

@ -0,0 +1,133 @@
import { test, expect } from "@odoo/hoot";
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
import { FloorScreen } from "@pos_restaurant/app/screens/floor_screen/floor_screen";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("getPosTable", async () => {
const store = await setupPosEnv();
store.currentFloor = store.models["restaurant.floor"].getFirst();
const screen = await mountWithCleanup(FloorScreen, {});
const renderedComp = await screen.env.services.renderer.toHtml(FloorScreen, {});
const table = screen.getPosTable(renderedComp.querySelector(".tableId-2"));
expect(table.id).toBe(store.currentFloor.table_ids[0].id);
});
test.tags("desktop");
test("computeFloorSize", async () => {
const store = await setupPosEnv();
const floor = store.models["restaurant.floor"].get(2);
store.currentFloor = floor;
store.floorPlanStyle = "default";
const screen = await mountWithCleanup(FloorScreen, {});
screen.floorScrollBox = {
el: {
clientHeight: 500,
offsetWidth: 700,
scrollTop: 0,
scrollLeft: 0,
},
};
screen.state.floorMapOffset = { x: 0, y: 0 };
screen.computeFloorSize();
expect(screen.state.floorWidth).toBe("927px");
expect(screen.state.floorHeight).toBe("500px");
});
test("resetTable", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(1);
const order = store.addNewOrder({ table_id: table });
store.setOrder(order);
const screen = await mountWithCleanup(FloorScreen, {});
await screen.resetTable();
expect(store.getOrder()).toBe(undefined);
});
test("pinch gesture computes scale and sets it", async () => {
await setupPosEnv();
const screen = await mountWithCleanup(FloorScreen, {});
screen.getScale = () => 1;
let scaleValue = null;
screen.setScale = (value) => {
scaleValue = value;
};
const startEvent = {
touches: [
{ pageX: 0, pageY: 0 },
{ pageX: 0, pageY: 100 },
],
currentTarget: {
style: {
setProperty: () => {},
},
},
};
screen._onPinchStart(startEvent);
const hypotStart = Math.hypot(0 - 0, 0 - 100);
expect(screen.scalehypot).toBe(hypotStart);
expect(screen.initalScale).toBe(1);
const moveEvent = {
touches: [
{ pageX: 0, pageY: 0 },
{ pageX: 0, pageY: 200 },
],
};
screen._computePinchHypo(moveEvent, screen.movePinch.bind(screen));
expect(scaleValue).toBeCloseTo(2);
});
test.tags("desktop");
test("_createTableHelper", async () => {
const store = await setupPosEnv();
const floor = store.models["restaurant.floor"].get(2);
const screen = await mountWithCleanup(FloorScreen, {});
screen.selectFloor(floor);
const table = await screen._createTableHelper(null);
expect(Boolean(table)).toBe(true);
expect(table.floor_id.id).toBe(floor.id);
expect(table.table_number).toBe(5);
expect(table.height).toBe(table.width);
expect(table.position_v >= 0).toBe(true);
expect(table.position_h >= 10).toBe(true);
});
test("_getNewTableNumber", async () => {
const store = await setupPosEnv();
const floor = store.models["restaurant.floor"].getFirst(); // Main Floor (tables 1,2,4)
const screen = await mountWithCleanup(FloorScreen, {});
screen.selectFloor(floor);
const newNumber = screen._getNewTableNumber();
expect(newNumber).toBe(5); // max(1,2,4) + 1 = 5
});
test("duplicateTable", async () => {
const store = await setupPosEnv();
const floor = store.models["restaurant.floor"].getFirst();
const screen = await mountWithCleanup(FloorScreen, {});
screen.selectFloor(floor);
screen.state.selectedTableIds = [floor.table_ids[0].id];
await screen.duplicateTable();
expect(screen.state.selectedTableIds.length).toBe(1);
const newTableId = screen.state.selectedTableIds[0];
expect(newTableId).not.toBe(floor.table_ids[0].id);
});
test("_isTableVisible", async () => {
const store = await setupPosEnv();
const floor = store.models["restaurant.floor"].getFirst();
const screen = await mountWithCleanup(FloorScreen, {});
screen.selectFloor(floor);
screen.floorScrollBox = {
el: {
scrollTop: 0,
scrollLeft: 0,
clientHeight: 500,
clientWidth: 500,
},
};
const table = store.models["restaurant.table"].get(2);
expect(screen._isTableVisible(table)).toBe(true);
});

View file

@ -0,0 +1,129 @@
import { describe, test, expect } from "@odoo/hoot";
import { SplitBillScreen } from "@pos_restaurant/app/screens/split_bill_screen/split_bill_screen";
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("_getSplitOrderName", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const screen = await mountWithCleanup(SplitBillScreen, {
props: {
orderUuid: order.uuid,
},
});
const originalName = "T1";
const result = screen._getSplitOrderName(originalName);
expect(result).toBe("T1B");
});
describe("onClickLine", () => {
test("increments quantity and price tracker on regular line", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const screen = await mountWithCleanup(SplitBillScreen, {
props: {
orderUuid: order.uuid,
},
});
const line = order.getOrderlines()[0];
screen.onClickLine(line);
expect(screen.qtyTracker[line.uuid]).toBe(1);
expect(screen.priceTracker[line.uuid] > 0).toBe(true);
screen.onClickLine(line);
expect(screen.qtyTracker[line.uuid]).toBe(2);
});
test("handles combo line and its child lines", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const comboTemplate = store.models["product.template"].get(7);
const comboItem1 = store.models["product.combo.item"].get(1);
const comboItem2 = store.models["product.combo.item"].get(3);
const line = await store.addLineToOrder(
{
product_tmpl_id: comboTemplate,
payload: [
[
{
combo_item_id: comboItem1,
qty: 1,
},
{
combo_item_id: comboItem2,
qty: 1,
},
],
[],
],
configure: true,
},
order
);
expect(order.lines.length).toBe(3);
expect(line.product_id.product_tmpl_id).toBe(comboTemplate);
expect(line.combo_line_ids.length).toBe(2);
expect(line.combo_line_ids[0].product_id.id).toBe(comboItem1.product_id.id);
expect(line.combo_line_ids[1].product_id.id).toBe(comboItem2.product_id.id);
const screen = await mountWithCleanup(SplitBillScreen, {
props: {
orderUuid: order.uuid,
},
});
screen.onClickLine(order.lines[0]);
expect(screen.qtyTracker[order.lines[0].uuid]).toBe(1);
expect(screen.qtyTracker[order.lines[1].uuid]).toBe(1);
expect(screen.qtyTracker[order.lines[2].uuid]).toBe(1);
});
});
test("_getOrderName", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const screen = await mountWithCleanup(SplitBillScreen, {
props: {
orderUuid: order.uuid,
},
});
expect(screen._getOrderName({ table_id: { table_number: 3 } })).toBe("3");
expect(screen._getOrderName({ floatingOrderName: "ToGo" })).toBe("ToGo");
expect(screen._getOrderName({})).toBe("");
});
test("setLineQtyStr", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const screen = await mountWithCleanup(SplitBillScreen, {
props: {
orderUuid: order.uuid,
},
});
const line = order.getOrderlines()[0];
screen.qtyTracker[line.uuid] = 2;
screen.setLineQtyStr(line);
expect(line.uiState.splitQty).toBe("2 / 3");
});
test("createSplittedOrder", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const table = store.models["restaurant.table"].get(2);
order.table_id = table;
const screen = await mountWithCleanup(SplitBillScreen, {
props: {
orderUuid: order.uuid,
},
});
const line = order.getOrderlines()[0];
screen.qtyTracker[line.uuid] = 2;
const originalUUID = order.uuid;
await screen.createSplittedOrder();
const currentOrder = store.getOrder();
expect(currentOrder.floating_order_name).toBe("1B");
expect(currentOrder.uuid).not.toBe(originalUUID);
expect(currentOrder.getOrderlines().length).toBe(1);
expect(currentOrder.getOrderlines()[0].getQuantity()).toBe(2);
expect(order.getOrderlines()[0].getQuantity()).toBe(1);
});

View file

@ -0,0 +1,59 @@
import { test, expect } from "@odoo/hoot";
import { TipScreen } from "@pos_restaurant/app/screens/tip_screen/tip_screen";
import { mountWithCleanup, MockServer } from "@web/../tests/web_test_helpers";
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("validateTip", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const cardPaymentMethod = store.models["pos.payment.method"].get(2);
order.addPaymentline(cardPaymentMethod);
await store.syncAllOrders();
TipScreen.prototype.printTipReceipt = async () => {};
const screen = await mountWithCleanup(TipScreen, {
props: {
orderUuid: order.uuid,
},
});
screen.state.inputTipAmount = "2";
await screen.validateTip();
expect(order.is_tipped).toBe(true);
expect(order.tip_amount).toBe(2);
const tipLine = order.lines.find(
(line) => line.product_id.id === store.config.tip_product_id.id
);
store.data.write("pos.order.line", [tipLine.id], {
write_date: luxon.DateTime.now(),
});
MockServer.env["pos.order.line"].write([tipLine.id], {
order_id: order.id,
write_date: luxon.DateTime.now(),
});
expect(Boolean(tipLine)).toBe(true);
expect(tipLine.price_unit).toBe(2);
await store.removeOrder(order);
});
test("overallAmountStr", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const cardPaymentMethod = store.models["pos.payment.method"].get(2);
order.addPaymentline(cardPaymentMethod);
await store.syncAllOrders();
TipScreen.prototype.printTipReceipt = async () => {};
const screen = await mountWithCleanup(TipScreen, {
props: {
orderUuid: order.uuid,
},
});
screen.state.inputTipAmount = "2";
const result = screen.overallAmountStr;
const total = order.priceIncl;
const original = screen.env.utils.formatCurrency(total);
const tip = screen.env.utils.formatCurrency(2);
const overall = screen.env.utils.formatCurrency(total + 2);
expect(result).toBe(`${original} + ${tip} tip = ${overall}`);
});

View file

@ -0,0 +1,11 @@
import { PosConfig } from "@point_of_sale/../tests/unit/data/pos_config.data";
PosConfig._records = PosConfig._records.map((record) => ({
...record,
module_pos_restaurant: true,
floor_ids: [2, 3],
iface_tipproduct: true,
tip_product_id: 1,
set_tip_after_payment: true,
default_screen: "tables",
}));

View file

@ -0,0 +1,8 @@
import { patch } from "@web/core/utils/patch";
import { PosOrderLine } from "@point_of_sale/../tests/unit/data/pos_order_line.data";
patch(PosOrderLine.prototype, {
_load_pos_data_fields() {
return [...super._load_pos_data_fields(), "course_id"];
},
});

View file

@ -0,0 +1,8 @@
import { patch } from "@web/core/utils/patch";
import { PosPreset } from "@point_of_sale/../tests/unit/data/pos_preset.data";
patch(PosPreset.prototype, {
_load_pos_data_fields() {
return [...super._load_pos_data_fields(), "use_guest"];
},
});

View file

@ -0,0 +1,13 @@
import { PosSession } from "@point_of_sale/../tests/unit/data/pos_session.data";
import { patch } from "@web/core/utils/patch";
patch(PosSession.prototype, {
_load_pos_data_models() {
return [
...super._load_pos_data_models(),
"restaurant.floor",
"restaurant.table",
"restaurant.order.course",
];
},
});

View file

@ -0,0 +1,41 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
export class RestaurantFloor extends models.ServerModel {
_name = "restaurant.floor";
_load_pos_data_fields() {
return [
"name",
"background_color",
"table_ids",
"sequence",
"pos_config_ids",
"floor_background_image",
];
}
_records = [
{
id: 2,
name: "Main Floor",
background_color: "red",
table_ids: [2, 3, 4],
sequence: 1,
pos_config_ids: [1],
floor_background_image: false,
},
{
id: 3,
name: "Patio",
background_color: "rgb(130, 233, 171)",
table_ids: [14, 15, 16],
sequence: 1,
pos_config_ids: [1],
floor_background_image: false,
},
];
}
patch(hootPosModels, [...hootPosModels, RestaurantFloor]);

View file

@ -0,0 +1,13 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
export class RestaurantOrderCourse extends models.ServerModel {
_name = "restaurant.order.course";
_load_pos_data_fields() {
return ["uuid", "fired", "order_id", "line_ids", "index", "write_date"];
}
}
patch(hootPosModels, [...hootPosModels, RestaurantOrderCourse]);

View file

@ -0,0 +1,112 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
export class RestaurantTable extends models.ServerModel {
_name = "restaurant.table";
_load_pos_data_fields() {
return [
"table_number",
"width",
"height",
"position_h",
"position_v",
"parent_id",
"shape",
"floor_id",
"color",
"seats",
"active",
];
}
_records = [
{
id: 2,
table_number: 1,
width: 90,
height: 90,
position_h: 407,
position_v: 88,
parent_id: false,
shape: "square",
floor_id: 2,
color: "rgb(53,211,116)",
seats: 4,
active: true,
},
{
id: 3,
table_number: 2,
width: 90,
height: 90,
position_h: 732,
position_v: 221,
parent_id: false,
shape: "square",
floor_id: 2,
color: "rgb(53,211,116)",
seats: 4,
active: true,
},
{
id: 4,
table_number: 4,
width: 165,
height: 100,
position_h: 762,
position_v: 83,
parent_id: false,
shape: "square",
floor_id: 2,
color: "rgb(53,211,116)",
seats: 4,
active: true,
},
{
id: 14,
table_number: 101,
width: 130,
height: 85,
position_h: 100,
position_v: 50,
parent_id: false,
shape: "square",
floor_id: 3,
color: "rgb(53,211,116)",
seats: 2,
active: true,
},
{
id: 15,
table_number: 102,
width: 130,
height: 85,
position_h: 100,
position_v: 166,
parent_id: false,
shape: "square",
floor_id: 3,
color: "rgb(53,211,116)",
seats: 2,
active: true,
},
{
id: 16,
table_number: 103,
width: 130,
height: 85,
position_h: 100,
position_v: 283,
parent_id: false,
shape: "square",
floor_id: 3,
color: "rgb(53,211,116)",
seats: 2,
active: true,
},
];
}
patch(hootPosModels, [...hootPosModels, RestaurantTable]);

View file

@ -0,0 +1,74 @@
import { test, describe, expect } from "@odoo/hoot";
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
describe("pos.order restaurant patches", () => {
test("customer count and amount per guest", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
order.setCustomerCount(3);
expect(order.getCustomerCount()).toBe(3);
order.setCustomerCount(4);
expect(order.amountPerGuest()).toBe(4.4625);
});
test("isDirectSale", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
expect(order.isDirectSale).toBe(true);
});
test("setPartner", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const partner = store.models["res.partner"].get(18);
order.setPartner(partner);
expect(order.floating_order_name).toBe("Public user");
});
test("cleanCourses", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const course1 = store.addCourse();
const line = order.lines[0];
line.course_id = course1;
const course2 = store.addCourse();
course1.fired = true;
order.cleanCourses();
expect(order.course_ids.includes(course2)).toBe(false);
expect(order.course_ids.includes(course1)).toBe(true);
});
test("getNextCourseIndex", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
store.addCourse();
store.addCourse();
expect(order.getNextCourseIndex()).toBe(4);
});
test("getName returns formatted name for table + children", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
const order = store.addNewOrder({ table_id: table });
const child = store.models["restaurant.table"].get(3);
let name = order.getName();
expect(name).toBe("T 1");
child.parent_id = table;
name = order.getName();
expect(name).toBe("T 1 & 2");
});
test("ensureCourseSelection and getSelectedCourse", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const course1 = store.addCourse();
course1.fired = false;
const course2 = store.addCourse();
course2.fired = true;
order.ensureCourseSelection();
expect(order.getSelectedCourse().uuid).toBe(course1.uuid);
});
});

View file

@ -0,0 +1,38 @@
import { test, expect } from "@odoo/hoot";
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("name returns localized name with index", async () => {
const store = await setupPosEnv();
store.addNewOrder();
const course = store.addCourse();
expect(course.name).toBe("Course 1");
});
test("isSelected", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const course = store.addCourse();
order.selectCourse(course);
expect(course.isSelected()).toBe(true);
});
test("isEmpty", async () => {
const store = await setupPosEnv();
store.addNewOrder();
const course = store.addCourse();
course.line_ids = [];
expect(course.isEmpty()).toBe(true);
});
test("isReadyToFire", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const course = store.addCourse();
const line = order.lines[0];
line.course_id = course;
course.line_ids = [line];
expect(course.isReadyToFire()).toBe(true);
});

View file

@ -0,0 +1,62 @@
import { test, expect } from "@odoo/hoot";
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("getOrders", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
const order = store.addNewOrder({ table_id: table });
const tableOrders = table.getOrders();
expect(tableOrders.length).toBe(1);
expect(tableOrders[0].id).toBe(order.id);
});
test("getParent and isParent", async () => {
const store = await setupPosEnv();
const models = store.models;
const parent = models["restaurant.table"].get(2);
const child = models["restaurant.table"].get(3);
child.parent_id = parent;
const result = child.getParent();
expect(parent.isParent(child)).toBe(true);
expect(result.id).toBe(2);
});
test("getParentSide", async () => {
const store = await setupPosEnv();
const models = store.models;
const parent = models["restaurant.table"].get(2);
const child = models["restaurant.table"].get(3);
child.parent_id = parent;
child.position_h = parent.position_h + 50;
child.position_v = parent.position_v;
const side = child.getParentSide();
expect(side).toBe("left");
});
test("getX and getY", async () => {
const store = await setupPosEnv();
const table1 = store.models["restaurant.table"].get(2);
const table2 = store.models["restaurant.table"].get(3);
expect(table1.getX()).toBe(407);
expect(table1.getY()).toBe(88);
table2.parent_id = table1;
table2.parent_side = "left";
expect(table2.getX()).toBe(497);
expect(table2.getY()).toBe(88);
table2.parent_side = "top";
expect(table2.getX()).toBe(407);
expect(table2.getY()).toBe(-2);
});
test("rootTable", async () => {
const store = await setupPosEnv();
const table1 = store.models["restaurant.table"].get(2);
const table2 = store.models["restaurant.table"].get(3);
const table3 = store.models["restaurant.table"].get(4);
table2.parent_id = table1;
table3.parent_id = table2;
expect(table3.rootTable.id).toBe(table1.id);
});

View file

@ -0,0 +1,466 @@
import { describe, expect, test } from "@odoo/hoot";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import {
getFilledOrder,
setupPosEnv,
waitUntilOrdersSynced,
} from "@point_of_sale/../tests/unit/utils";
import { MockServer } from "@web/../tests/web_test_helpers";
const { DateTime } = luxon;
definePosModels();
describe("restaurant pos_store.js", () => {
test("restoreOrdersToOriginalTable", async () => {
const store = await setupPosEnv();
const table1 = store.models["restaurant.table"].get(1);
const table2 = store.models["restaurant.table"].get(2);
const sourceOrder = store.addNewOrder({ table_id: table1 });
const product = store.models["product.template"].get(5);
await store.addLineToOrder(
{
product_tmpl_id: product,
qty: 3,
},
sourceOrder
);
const line = sourceOrder.lines[0];
sourceOrder.uiState.unmerge = {
[line.uuid]: {
table_id: table2.id,
quantity: 1,
},
};
const newOrder = await store.restoreOrdersToOriginalTable(sourceOrder, table2);
expect(newOrder.table_id.id).toBe(table2.id);
expect(newOrder.lines.length).toBe(1);
});
test("fireCourse", async () => {
const store = await setupPosEnv();
store.addNewOrder();
const course = store.addCourse();
store.printCourseTicket = async () => true;
const result = await store.fireCourse(course);
expect(course.fired).toBe(true);
expect(result).toBe(true);
});
test("setTable", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
const blankOrder = store.addNewOrder();
expect(blankOrder.table_id).toBe(undefined);
await store.setTable(table);
expect(blankOrder.table_id.id).toBe(table.id);
expect(store.getOrder().id).toBe(blankOrder.id);
});
test("computeTableCount", async () => {
const store = await setupPosEnv();
const order1 = store.addNewOrder();
const table = store.models["restaurant.table"].get(2);
expect(table.uiState.orderCount).toBe(0);
order1.table_id = table;
store.computeTableCount();
expect(table.uiState.orderCount).toBe(1);
});
test("sync dirty order when unsetting table", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
const order = await getFilledOrder(store);
order.table_id = table;
expect(store.getPendingOrder().orderToCreate).toHaveLength(1);
await store.unsetTable();
await waitUntilOrdersSynced(store);
expect(store.getPendingOrder().orderToCreate).toHaveLength(0);
expect(order.isDirty()).toBe(false);
//Update the order
order.setInternalNote("Test note");
expect(order.isDirty()).toBe(true);
await store.unsetTable();
await waitUntilOrdersSynced(store);
expect(order.isDirty()).toBe(false);
expect(store.getPendingOrder().orderToUpdate).toHaveLength(0);
});
describe("class DevicesSynchronisation", () => {
test("Synchronization for a filled table has arrived", async () => {
// If a local order is already create on a table when another device send another order
// for the same table, we merge the orderlines of the local order with the synced order.
const store = await setupPosEnv();
const sync = store.deviceSync;
const table = store.models["restaurant.table"].get(2);
const filledOrder = await getFilledOrder(store);
const product1 = filledOrder.lines[0].product_id;
const product2 = filledOrder.lines[1].product_id;
filledOrder.table_id = table;
expect(table.getOrder()).toBe(filledOrder);
MockServer.env["pos.order"].create({
config_id: store.config.id,
session_id: store.session.id,
table_id: table.id,
lines: filledOrder.lines.map((line) => [
0,
0,
{
product_id: line.product_id.id,
price_unit: line.price_unit,
qty: 1,
},
]),
});
await sync.collect({
static_records: {},
session_id: 1,
device_identifier: 0,
records: {},
});
expect(store.models["pos.order"].length).toEqual(1);
const order = store.models["pos.order"].get(1);
expect(order.lines).toHaveLength(4);
expect(table.getOrders()).toHaveLength(1);
expect(order.lines[0].product_id).toEqual(product1);
expect(order.lines[1].product_id).toEqual(product2);
expect(order.lines[2].product_id).toEqual(product1);
expect(order.lines[3].product_id).toEqual(product2);
expect(order.lines[0].qty).toEqual(1);
expect(order.lines[1].qty).toEqual(1);
expect(order.lines[2].qty).toEqual(3);
expect(order.lines[3].qty).toEqual(2);
expect(order.lines[0].id).toBeOfType("number");
expect(order.lines[1].id).toBeOfType("number");
expect(order.lines[2].id).toBeOfType("string");
expect(order.lines[3].id).toBeOfType("string");
await store.syncAllOrders();
expect(order.lines[0].id).toBeOfType("number");
expect(order.lines[1].id).toBeOfType("number");
expect(order.lines[2].id).toBeOfType("number");
expect(order.lines[3].id).toBeOfType("number");
});
test("Orders must be downloaded by opening a table.", async () => {
const store = await setupPosEnv();
const filledOrder = await getFilledOrder(store);
const table = store.models["restaurant.table"].get(2);
MockServer.env["pos.order"].create({
config_id: store.config.id,
session_id: store.session.id,
table_id: table.id,
lines: filledOrder.lines.map((line) => [
0,
0,
{
product_id: line.product_id.id,
price_unit: line.price_unit,
qty: 1,
},
]),
});
// This function is called by setTable in pos_store, but it is not awaited.
// So we need to await it here to ensure the test runs correctly.
await store.deviceSync.readDataFromServer();
expect(table.getOrder().id).toEqual(1);
});
test("Orders updated from another device must be synchronized directly.", async () => {
const store = await setupPosEnv();
const filledOrder = await getFilledOrder(store);
const table = store.models["restaurant.table"].get(2);
filledOrder.table_id = table;
await store.syncAllOrders();
expect(filledOrder.id).toBeOfType("number");
expect(filledOrder.lines).toHaveLength(2);
expect(filledOrder.table_id).toBe(table);
MockServer.env["pos.order"].write([filledOrder.id], {
lines: filledOrder.lines.map((line) => [
0,
0,
{
product_id: line.product_id.id,
price_unit: line.price_unit,
qty: 40,
},
]),
});
await store.deviceSync.readDataFromServer();
expect(filledOrder.lines).toHaveLength(4);
expect(filledOrder.lines[2].qty).toEqual(40);
expect(filledOrder.lines[3].qty).toEqual(40);
expect(filledOrder.lines[2].id).toBeOfType("number");
expect(filledOrder.lines[3].id).toBeOfType("number");
});
test("Data from other devices overrides local data", async () => {
const store = await setupPosEnv();
const filledOrder = await getFilledOrder(store);
const table = store.models["restaurant.table"].get(2);
filledOrder.table_id = table;
filledOrder.internal_note = "Hey give me a discount!";
await store.syncAllOrders();
expect(filledOrder.id).toBeOfType("number");
expect(filledOrder.internal_note).toEqual("Hey give me a discount!");
filledOrder.internal_note = "Hey give me a discount! But not too much!";
MockServer.env["pos.order"].write([filledOrder.id], {
internal_note: "Hey give me a discount!",
});
await store.deviceSync.readDataFromServer();
expect(filledOrder.internal_note).toEqual("Hey give me a discount!");
});
test("There should only be one order per table.", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
const date = DateTime.now().toFormat("yyyy-MM-dd HH:mm:ss");
const filledOrder = await getFilledOrder(store);
filledOrder.table_id = table;
await store.syncAllOrders();
let id = 1;
let lineId = 1;
const createOrderForTable = async () => {
const orderId = `${id++}_string`;
const lines = [
{
id: `${lineId++}_string`,
order_id: orderId,
product_id: 5,
qty: 1,
write_date: date,
},
{
id: `${lineId++}_string`,
order_id: orderId,
product_id: 6,
qty: 1,
write_date: date,
},
];
const order = [
{
id: orderId,
lines: lines.map((line) => line.id),
write_date: date,
table_id: table.id,
pos_reference: "000-0-000000",
session_id: store.session.id,
config_id: store.config.id,
},
];
const newData = {
"pos.order": order,
"pos.order.line": lines,
};
await store.deviceSync.processDynamicRecords(newData);
};
for (let i = 0; i < 8; i++) {
await createOrderForTable();
expect(table.getOrders()).toHaveLength(1);
expect(table.getOrder().id).toBeOfType("number");
expect(table.getOrder().lines).toHaveLength(4 + i * 2);
}
expect(store.models["pos.order"].length).toEqual(1);
});
});
describe("categoryCount", () => {
test("Normal flow", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
order.lines[0].note = '[{"text":"Test Note","colorIndex":0}]';
order.lines[1].note =
'[{"text":"Test 1","colorIndex":0},{"text":"Test 2","colorIndex":0}]';
order.general_customer_note = '[{"text":"General Note","colorIndex":0}]';
const changes = store.categoryCount;
expect(changes).toEqual([
{ count: 3, name: "Category 1" },
{ count: 2, name: "Category 2" },
{ count: 1, name: "Message" },
]);
});
test("Unselected order", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
order.general_customer_note = '[{"text":"General Note","colorIndex":0}]';
store.selectedOrderUuid = null;
// without a selected order, `categoryCount` throws
expect(() => store.categoryCount).toThrow();
// explicitly specify the order to compute the changes for
const changes = store.getCategoryCount(order);
expect(changes).toEqual([
{ count: 3, name: "Category 1" },
{ count: 2, name: "Category 2" },
{ count: 1, name: "Message" },
]);
});
});
test("getDefaultSearchDetails", async () => {
const store = await setupPosEnv();
const result = store.getDefaultSearchDetails();
expect(result).toEqual({
fieldName: "REFERENCE",
searchTerm: "",
});
});
test("findTable", async () => {
const store = await setupPosEnv();
const table1 = store.models["restaurant.table"].get(2);
const floor = store.models["restaurant.floor"].get(2);
store.currentFloor = floor;
const result = store.findTable("1");
expect(result.id).toBe(table1.id);
});
test("searchOrder", async () => {
const store = await setupPosEnv();
const floor = store.models["restaurant.floor"].get(2);
store.currentFloor = floor;
const found = store.searchOrder("2");
expect(found).toBe(true);
const notFound = store.searchOrder("999");
expect(notFound).toBe(false);
});
test("getTableOrders", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
store.addNewOrder({ table_id: table });
const orders = store.getTableOrders(table.id);
expect(orders.length).toBe(1);
});
test("getActiveOrdersOnTable", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
store.addNewOrder({ table_id: table });
store.addNewOrder({ table_id: table });
const orders = await store.getActiveOrdersOnTable(table);
expect(orders.length).toBe(2);
});
test("prepareOrderTransfer", async () => {
const store = await setupPosEnv();
const tableSrc = store.models["restaurant.table"].get(1);
const tableDst = store.models["restaurant.table"].get(2);
const order = store.addNewOrder({ table_id: tableSrc });
store.alert = {
dismiss: () => {},
};
const result = store.prepareOrderTransfer(order, tableDst);
expect(result).toBe(false);
expect(order.table_id).toBe(tableDst);
expect(store.getOrder()).toBe(order);
});
test("transferOrder", async () => {
const store = await setupPosEnv();
const tableSrc = store.models["restaurant.table"].get(1);
const tableDst = store.models["restaurant.table"].get(2);
const sourceOrder = store.addNewOrder({ table_id: tableSrc });
const product1 = store.models["product.template"].get(5);
await store.addLineToOrder(
{
product_tmpl_id: product1,
qty: 2,
},
sourceOrder
);
const order = store.addNewOrder({ table_id: tableDst });
await store.transferOrder(sourceOrder.uuid, tableDst);
expect(sourceOrder.lines.length).toBe(0);
expect(order.lines.length).toBe(1);
expect(order.table_id.id).toBe(tableDst.id);
});
test("mergeOrders merges lines and courses", async () => {
const store = await setupPosEnv();
const models = store.models;
const table1 = models["restaurant.table"].get(2);
const table2 = models["restaurant.table"].get(3);
const order1 = store.addNewOrder({ table_id: table1 });
const course1 = store.addCourse();
const product1 = models["product.template"].get(5);
const line1 = await store.addLineToOrder({ product_tmpl_id: product1, qty: 1 }, order1);
line1.course_id = course1;
course1.line_ids = [line1];
const order2 = store.addNewOrder({ table_id: table2 });
const course2 = store.addCourse();
const product2 = models["product.template"].get(6);
const line2 = await store.addLineToOrder({ product_tmpl_id: product2, qty: 2 }, order2);
line2.course_id = course2;
course2.line_ids = [line2];
await store.mergeOrders(order1, order2);
expect(order2.lines.length).toBe(2);
expect(order1.lines.length).toBe(0);
expect(order1.table_id).toBe(undefined);
expect(order2.table_id.id).toBe(table2.id);
expect(order2.course_ids.length).toBe(1);
expect(line2.course_id.id).toBe(course2.id);
});
test("mergeOrders sums guest counts", async () => {
const store = await setupPosEnv();
const models = store.models;
const table1 = models["restaurant.table"].get(2);
const table2 = models["restaurant.table"].get(3);
const order1 = store.addNewOrder({ table_id: table1 });
order1.setCustomerCount(3);
const order2 = store.addNewOrder({ table_id: table2 });
order2.setCustomerCount(5);
await store.mergeOrders(order1, order2);
expect(order2.getCustomerCount()).toBe(8);
});
test("getCustomerCount", async () => {
const store = await setupPosEnv();
const table = store.models["restaurant.table"].get(2);
store.addNewOrder({ table_id: table }).setCustomerCount(3);
store.addNewOrder({ table_id: table }).setCustomerCount(6);
const count = store.getCustomerCount(table.id);
expect(count).toBe(9);
});
test("firstPage", async () => {
const store = await setupPosEnv();
expect(store.firstPage.page).toBe("LoginScreen");
});
describe("addCourse", () => {
test("creates first course and selects it", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const course = store.addCourse();
expect(course.order_id).toBe(order);
expect(order.getSelectedCourse()).toBe(course);
});
test("creates second course and assigns existing lines to first", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const product = store.models["product.template"].get(5);
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
const course1 = store.addCourse();
const course2 = order.getSelectedCourse();
expect(order.course_ids.length).toBe(2);
expect(course1).not.toBe(course2);
expect(order.lines[0].course_id).toBe(course1);
});
});
});