mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 03:12:02 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -0,0 +1,110 @@
|
|||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import * as CustomerDisplay from "@point_of_sale/../tests/customer_display/customer_display_utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { isVisible } from "@web/core/utils/ui";
|
||||
|
||||
registry.category("web_tour.tours").add("CustomerDisplayTour", {
|
||||
steps: () =>
|
||||
[
|
||||
CustomerDisplay.addProduct(CustomerDisplay.ADD_PRODUCT, "add product"),
|
||||
Order.hasLine({ productName: "Letter Tray", price: "2,972.75" }),
|
||||
{
|
||||
content: "An order line with `isSelected: false` should not have 'selected' class",
|
||||
trigger: ".order-container .orderline:last-child:not(.selected)",
|
||||
},
|
||||
CustomerDisplay.amountIs("Total", "2,972.75"),
|
||||
CustomerDisplay.postMessage(CustomerDisplay.PAY_WITH_CASH, "pay with cash"),
|
||||
CustomerDisplay.amountIs("Cash", "2,972.75"),
|
||||
CustomerDisplay.postMessage(CustomerDisplay.ORDER_IS_FINALIZED, "order is finalized"),
|
||||
{
|
||||
content: "Check that we are now on the 'Thank you' screen",
|
||||
trigger: "div:contains('Thank you.')",
|
||||
},
|
||||
CustomerDisplay.postMessage(CustomerDisplay.NEW_ORDER, "new order"),
|
||||
{
|
||||
trigger: " div:contains('Welcome.')",
|
||||
},
|
||||
Order.doesNotHaveLine({}),
|
||||
CustomerDisplay.amountIs("Total", "0.00"),
|
||||
{
|
||||
trigger: "body",
|
||||
run: () =>
|
||||
CustomerDisplay.postMessage(
|
||||
CustomerDisplay.ADD_PRODUCT_SELECTED,
|
||||
"add products"
|
||||
).run(),
|
||||
},
|
||||
{
|
||||
content: "An order line with `isSelected: true` should have 'selected' class",
|
||||
trigger: ".order-container .orderline:last-child.selected",
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("CustomerDisplayTourScroll", {
|
||||
steps: () =>
|
||||
[
|
||||
CustomerDisplay.addProduct(CustomerDisplay.ADD_MULTI_PRODUCTS, "add 20 products"),
|
||||
{
|
||||
content: "An order line with `isSelected: true` should have 'selected' class",
|
||||
trigger: ".order-container .orderline:last-child.selected",
|
||||
run: async () =>
|
||||
await new Promise((resolve) => {
|
||||
const orderLine = document.querySelector(
|
||||
".order-container .orderline:last-child.selected"
|
||||
);
|
||||
const animationDuration = parseFloat(
|
||||
getComputedStyle(orderLine).animationDuration
|
||||
);
|
||||
if (animationDuration === 0) {
|
||||
return resolve();
|
||||
}
|
||||
orderLine.onanimationend = function (event) {
|
||||
if (event.target === orderLine && event.animationName === "item_in") {
|
||||
resolve(event);
|
||||
}
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
content: "The order container should have scrolled to show the selected order line",
|
||||
trigger: ".order-container",
|
||||
run: async () => {
|
||||
const orderContainer = document.querySelector(".order-container");
|
||||
const orderLine = document.querySelector(
|
||||
".order-container .orderline:last-child.selected"
|
||||
);
|
||||
await new Promise((resolve) => {
|
||||
const checkScroll = () => {
|
||||
requestAnimationFrame(() => {
|
||||
if (orderContainer.scrollTop > 0 && isVisible(orderLine)) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkScroll, 1000);
|
||||
}
|
||||
});
|
||||
};
|
||||
checkScroll();
|
||||
});
|
||||
},
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("CustomerDisplayTourWithQr", {
|
||||
steps: () =>
|
||||
[
|
||||
CustomerDisplay.addProduct(CustomerDisplay.ADD_PRODUCT, "add product"),
|
||||
Order.hasLine({ productName: "Letter Tray", price: "2,972.75" }),
|
||||
CustomerDisplay.amountIs("Total", "2,972.75"),
|
||||
CustomerDisplay.postMessage(CustomerDisplay.PAY_WITH_CARD, "pay with card"),
|
||||
CustomerDisplay.postMessage(CustomerDisplay.SEND_QR, "send qr code"),
|
||||
{ trigger: "img[alt='QR Code']" },
|
||||
CustomerDisplay.postMessage(CustomerDisplay.PAY_WITH_CARD, "confirm payment"),
|
||||
CustomerDisplay.postMessage(CustomerDisplay.ORDER_IS_FINALIZED, "order is finalized"),
|
||||
{
|
||||
content: "Check that we are now on the 'Thank you' screen",
|
||||
trigger: "div:contains('Thank you.')",
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
import { run } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
export function postMessage(message, description = "") {
|
||||
return run(() => {
|
||||
window.customerDisplayChannel.postMessage(
|
||||
typeof message === "string" ? JSON.parse(message) : message
|
||||
);
|
||||
}, `send message to customer display: ${description}, with value: ${message}`);
|
||||
}
|
||||
|
||||
export function amountIs(method, amount) {
|
||||
return {
|
||||
content: `Check that the ${method} amount is ${amount}`,
|
||||
trigger: `div.row:has(div:contains('${method}')):has(div:contains('${amount}'))`,
|
||||
};
|
||||
}
|
||||
|
||||
export function addProduct(product, description = "") {
|
||||
return {
|
||||
trigger: "div:contains('Welcome.')",
|
||||
run: async () => {
|
||||
window.customerDisplayChannel = new BroadcastChannel("UPDATE_CUSTOMER_DISPLAY");
|
||||
postMessage(product, description).run();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const ADD_PRODUCT =
|
||||
'{"lines":[{"productName":"Letter Tray","price":"$ 2,972.75","qty":"1.00","unit":"Units","unitPrice":"$ 2,972.75","customerNote":"","internalNote":"[]","comboParent":"","packLotLines":[],"price_without_discount":"$ 2,972.75","isSelected":false,"imageSrc":"/web/image/product.product/855/image_128"}],"finalized":false,"amount":"2,972.75","paymentLines":[],"change":0,"onlinePaymentData":{}}';
|
||||
|
||||
export const ADD_PRODUCT_SELECTED =
|
||||
'{"lines":[{"productName":"Letter Tray","price":"$ 2,972.75","qty":"1.00","unit":"Units","unitPrice":"$ 2,972.75","customerNote":"","internalNote":"[]","comboParent":"","packLotLines":[],"price_without_discount":"$ 2,972.75","isSelected":true,"imageSrc":"/web/image/product.product/855/image_128"}],"finalized":false,"amount":"2,972.75","paymentLines":[],"change":0,"onlinePaymentData":{}}';
|
||||
|
||||
export const ADD_MULTI_PRODUCTS = (() => {
|
||||
const count = 20;
|
||||
const lines = Array.from({ length: count }, (_, i) => {
|
||||
const price = (Math.random() * 100 + 1).toFixed(2);
|
||||
return {
|
||||
productName: `Product ${i + 1}`,
|
||||
price: `$${price}`,
|
||||
qty: "1.00",
|
||||
unit: "Units",
|
||||
unitPrice: `$${price}`,
|
||||
customerNote: "",
|
||||
internalNote: "[]",
|
||||
comboParent: "",
|
||||
packLotLines: [],
|
||||
price_without_discount: `$${price}`,
|
||||
isSelected: i === count - 1,
|
||||
imageSrc: "/web/image/product.product/855/image_128",
|
||||
};
|
||||
});
|
||||
const amount = lines
|
||||
.reduce((sum, line) => sum + parseFloat(line.price.replace("$", "")), 0)
|
||||
.toFixed(2);
|
||||
return JSON.stringify({
|
||||
lines,
|
||||
finalized: false,
|
||||
amount,
|
||||
paymentLines: [],
|
||||
change: 0,
|
||||
onlinePaymentData: {},
|
||||
});
|
||||
})();
|
||||
|
||||
export const PAY_WITH_CASH =
|
||||
'{"lines":[{"productName":"Letter Tray","price":"$ 2,972.75","qty":"1.00","unit":"Units","unitPrice":"$ 2,972.75","customerNote":"","internalNote":"[]","comboParent":"","packLotLines":[],"price_without_discount":"$ 2,972.75","isSelected":true,"imageSrc":"/web/image/product.product/855/image_128"}],"finalized":false,"amount":"2,972.75","paymentLines":[{"name":"Cash","amount":"2,972.75"}],"change":0,"onlinePaymentData":{}}';
|
||||
|
||||
export const ORDER_IS_FINALIZED =
|
||||
'{"lines":[{"productName":"Letter Tray","price":"$ 2,972.75","qty":"1.00","unit":"Units","unitPrice":"$ 2,972.75","customerNote":"","internalNote":"[]","comboParent":"","packLotLines":[],"price_without_discount":"$ 2,972.75","isSelected":false,"imageSrc":"/web/image/product.product/855/image_128"}],"finalized":true,"amount":"2,972.75","paymentLines":[{"name":"Cash","amount":"2,972.75"}],"change":0,"onlinePaymentData":{}}';
|
||||
|
||||
export const NEW_ORDER =
|
||||
'{"lines":[],"finalized":false,"amount":"0.00","paymentLines":[],"change":0,"onlinePaymentData":{}}';
|
||||
|
||||
export const QR_URL =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
|
||||
|
||||
export const PAY_WITH_CARD = {
|
||||
lines: [
|
||||
{
|
||||
productName: "Letter Tray",
|
||||
price: "$ 2,972.75",
|
||||
qty: "1.00",
|
||||
unit: "Units",
|
||||
unitPrice: "$ 2,972.75",
|
||||
oldUnitPrice: "",
|
||||
customerNote: "",
|
||||
internalNote: "",
|
||||
comboParent: "",
|
||||
packLotLines: [],
|
||||
price_without_discount: "$ 2,972.75",
|
||||
isSelected: true,
|
||||
imageSrc: "/web/image/product.product/855/image_128",
|
||||
},
|
||||
],
|
||||
finalized: false,
|
||||
amount: "2,972.75",
|
||||
paymentLines: [{ name: "CARD", amount: "2,972.75" }],
|
||||
change: 0,
|
||||
onlinePaymentData: {},
|
||||
qrPaymentData: null,
|
||||
};
|
||||
|
||||
export const SEND_QR = {
|
||||
lines: [
|
||||
{
|
||||
productName: "Letter Tray",
|
||||
price: "$ 2,972.75",
|
||||
qty: "1.00",
|
||||
unit: "Units",
|
||||
unitPrice: "$ 2,972.75",
|
||||
oldUnitPrice: "",
|
||||
customerNote: "",
|
||||
internalNote: "",
|
||||
comboParent: "",
|
||||
packLotLines: [],
|
||||
price_without_discount: "$ 2,972.75",
|
||||
isSelected: true,
|
||||
imageSrc: "/web/image/product.product/855/image_128",
|
||||
},
|
||||
],
|
||||
finalized: false,
|
||||
amount: "2,972.75",
|
||||
paymentLines: [{ name: "CARD", amount: "2,972.75" }],
|
||||
change: 0,
|
||||
onlinePaymentData: {},
|
||||
qrPaymentData: {
|
||||
amount: "$ 2,972.75",
|
||||
name: "CARD",
|
||||
qrCode: QR_URL,
|
||||
},
|
||||
};
|
||||
|
||||
export const PAY_ONLINE = {
|
||||
lines: [
|
||||
{
|
||||
productName: "Letter Tray",
|
||||
price: "$ 2,972.75",
|
||||
qty: "1.00",
|
||||
unit: "Units",
|
||||
unitPrice: "$ 2,972.75",
|
||||
oldUnitPrice: "",
|
||||
customerNote: "",
|
||||
internalNote: "",
|
||||
comboParent: "",
|
||||
packLotLines: [],
|
||||
price_without_discount: "$ 2,972.75",
|
||||
isSelected: true,
|
||||
imageSrc: "/web/image/product.product/855/image_128",
|
||||
},
|
||||
],
|
||||
finalized: false,
|
||||
amount: "2,972.75",
|
||||
paymentLines: [{ name: "ONLINE", amount: "2,972.75" }],
|
||||
change: 0,
|
||||
onlinePaymentData: {
|
||||
formattedAmount: "$ 2,972.75",
|
||||
orderName: "/",
|
||||
qrCode: QR_URL,
|
||||
},
|
||||
};
|
||||
|
||||
export const PAID = {
|
||||
lines: [
|
||||
{
|
||||
productName: "Letter Tray",
|
||||
price: "$ 2,972.75",
|
||||
qty: "1.00",
|
||||
unit: "Units",
|
||||
unitPrice: "$ 2,972.75",
|
||||
oldUnitPrice: "",
|
||||
customerNote: "",
|
||||
internalNote: "",
|
||||
comboParent: "",
|
||||
packLotLines: [],
|
||||
price_without_discount: "$ 2,972.75",
|
||||
isSelected: true,
|
||||
imageSrc: "/web/image/product.product/855/image_128",
|
||||
},
|
||||
],
|
||||
finalized: false,
|
||||
amount: "2,972.75",
|
||||
paymentLines: [{ name: "ONLINE", amount: "2,972.75" }],
|
||||
change: 0,
|
||||
onlinePaymentData: {},
|
||||
};
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { Component, useState, xml } from "@odoo/owl";
|
||||
import { OdooLogo } from "@point_of_sale/app/components/odoo_logo/odoo_logo";
|
||||
import { CenteredIcon } from "@point_of_sale/app/components/centered_icon/centered_icon";
|
||||
import { Input } from "@point_of_sale/app/components/inputs/input/input";
|
||||
import { NumericInput } from "@point_of_sale/app/components/inputs/numeric_input/numeric_input";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { waitFor } from "@odoo/hoot-dom";
|
||||
|
||||
test("test that generic components can be mounted; the goal is to ensure that they don't have any unmet dependencies", async () => {
|
||||
class TestComponent extends Component {
|
||||
static props = [];
|
||||
static components = {
|
||||
OdooLogo,
|
||||
CenteredIcon,
|
||||
Input,
|
||||
NumericInput,
|
||||
};
|
||||
static template = xml`
|
||||
<div class="test-container">
|
||||
<OdooLogo />
|
||||
<CenteredIcon icon="'fa-smile'"/>
|
||||
<Input tModel="[state, 'number']"/>
|
||||
<NumericInput tModel="[state, 'number']" />
|
||||
</div>
|
||||
`;
|
||||
setup() {
|
||||
this.state = useState({ number: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("services").content = {};
|
||||
|
||||
await mountWithCleanup(TestComponent, {
|
||||
noMainContainer: true,
|
||||
});
|
||||
await waitFor("div.test-container");
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
export function confirm(confirmationText, button = ".btn-primary") {
|
||||
let trigger = `.modal:not(.o_inactive_modal) .modal-footer ${button}`;
|
||||
if (confirmationText) {
|
||||
trigger += `:contains("${confirmationText}")`;
|
||||
}
|
||||
return {
|
||||
content: "confirm dialog",
|
||||
trigger,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function cancel({ title } = {}) {
|
||||
return {
|
||||
content: "cancel dialog",
|
||||
trigger: `.modal .modal-header${
|
||||
title ? `:contains(${title})` : ""
|
||||
} button[aria-label="Close"]`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function discard() {
|
||||
return {
|
||||
content: "discard dialog",
|
||||
trigger: `.modal .modal-footer button:contains("Discard")`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function is({ title } = {}) {
|
||||
let trigger = ".modal .modal-content";
|
||||
if (title) {
|
||||
trigger += ` .modal-header:contains("${title}")`;
|
||||
}
|
||||
return {
|
||||
content: "dialog is open",
|
||||
trigger,
|
||||
};
|
||||
}
|
||||
export function isNot(...args) {
|
||||
const { trigger } = is(...args);
|
||||
return {
|
||||
content: "no dialog is open",
|
||||
trigger: negate(trigger),
|
||||
};
|
||||
}
|
||||
|
||||
export function bodyIs(body) {
|
||||
return {
|
||||
content: "dialog is open",
|
||||
trigger: `.modal-body:contains(${body})`,
|
||||
};
|
||||
}
|
||||
|
||||
export function footerBtnIsDisabled(buttonText) {
|
||||
return {
|
||||
content: `footer btn ${buttonText} should be disabled`,
|
||||
trigger: `.modal .modal-footer button:contains(${buttonText})[disabled]`,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export function has(text, type) {
|
||||
let trigger = `.o_notification:contains("${text}")`;
|
||||
if (type) {
|
||||
trigger += `:has(.o_notification_bar.bg-${type})`;
|
||||
}
|
||||
return {
|
||||
content: `Check if there is a notification with text "${text}"`,
|
||||
trigger,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
|
||||
export function enterValue(keys) {
|
||||
return Numpad.enterValue(keys).map((step) => ({
|
||||
...step,
|
||||
trigger: `.modal ${step.trigger}`,
|
||||
}));
|
||||
}
|
||||
export function isShown(val = "") {
|
||||
return [
|
||||
{
|
||||
content: `input shown is '${val}'`,
|
||||
trigger: `.modal .value:contains("${val}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { escapeRegExp } from "@web/core/utils/strings";
|
||||
|
||||
export const buttonTriger = (buttonValue) =>
|
||||
`div.numpad button:contains(/^${escapeRegExp(buttonValue)}$/)`; // regex to match the exact button value ( for ex: avoids matching "+10" instead of "1")
|
||||
|
||||
export const click = (buttonValue) => ({
|
||||
content: `click numpad button: ${buttonValue}`,
|
||||
trigger: buttonTriger(buttonValue),
|
||||
run: "click",
|
||||
});
|
||||
export const enterValue = (keys) => keys.split("").map((key) => click(key));
|
||||
export const isActive = (buttonValue) => ({
|
||||
content: `check if --${buttonValue}-- mode is activated`,
|
||||
trigger: `${buttonTriger(buttonValue)}.active`,
|
||||
});
|
||||
|
||||
export const isVisible = () => ({
|
||||
content: "check if numpad is visible",
|
||||
trigger: "div.numpad:visible",
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { run } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import { ConnectionLostError } from "@web/core/network/rpc";
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
const originalSend = XMLHttpRequest.prototype.send;
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
export function setOfflineMode() {
|
||||
return run(() => {
|
||||
window.fetch = () => {
|
||||
throw new ConnectionLostError();
|
||||
};
|
||||
XMLHttpRequest.prototype.send = () => {
|
||||
throw new ConnectionLostError();
|
||||
};
|
||||
console.error = (...args) => {
|
||||
const message = args[0] instanceof Error ? args[0].message : args[0];
|
||||
if (typeof message === "string" && message.includes("ConnectionLostError")) {
|
||||
console.info("Connection lost error handled in offline mode:", ...args);
|
||||
} else {
|
||||
originalConsoleError.apply(console, args);
|
||||
}
|
||||
};
|
||||
}, "Offline mode is now enabled");
|
||||
}
|
||||
|
||||
export function setOnlineMode() {
|
||||
return run(() => {
|
||||
window.fetch = originalFetch;
|
||||
XMLHttpRequest.prototype.send = originalSend;
|
||||
console.error = originalConsoleError;
|
||||
}, "Offline mode is now disabled");
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
/**
|
||||
* @typedef {{
|
||||
* withClass?: string, // ex: withClass: ".selected.blue"
|
||||
* withoutClass?: string,
|
||||
* run?: function | string,
|
||||
* productName?: string,
|
||||
* quantity?: string,
|
||||
* price?: string,
|
||||
* customerNote?: string,
|
||||
* comboParent?: string,
|
||||
* }} LineOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {LineOptions} options
|
||||
* @returns {import("@web_tour/js/tour_service").TourStep[]}
|
||||
*/
|
||||
export function hasLine({
|
||||
withClass = "",
|
||||
withoutClass = "",
|
||||
run = () => {},
|
||||
productName,
|
||||
quantity,
|
||||
price,
|
||||
priceUnit,
|
||||
customerNote,
|
||||
internalNote,
|
||||
comboParent,
|
||||
discount,
|
||||
oldPrice,
|
||||
priceNoDiscount,
|
||||
attributeLine,
|
||||
} = {}) {
|
||||
let trigger = `.order-container .orderline${withClass}`;
|
||||
if (withoutClass) {
|
||||
trigger += `:not(${withoutClass})`;
|
||||
}
|
||||
if (productName) {
|
||||
trigger += `:has(.product-name:contains("${productName}"))`;
|
||||
}
|
||||
if (quantity) {
|
||||
quantity = parseFloat(quantity) % 1 === 0 ? parseInt(quantity).toString() : quantity;
|
||||
trigger += `:has(.qty:contains("${quantity}"))`;
|
||||
}
|
||||
if (price) {
|
||||
trigger += `:has(.price:contains("${price}"))`;
|
||||
}
|
||||
if (priceUnit) {
|
||||
trigger += `:has(.price-per-unit:contains("${priceUnit}"))`;
|
||||
}
|
||||
if (customerNote) {
|
||||
trigger += `:has(.info-list .customer-note:contains("${customerNote}"))`;
|
||||
}
|
||||
if (internalNote) {
|
||||
trigger += `:has(.info-list .o_tag_badge_text:contains("${internalNote}"))`;
|
||||
}
|
||||
if (comboParent) {
|
||||
trigger += `:has(.info-list .combo-parent-name:contains("${comboParent}"))`;
|
||||
}
|
||||
if (discount || discount === "") {
|
||||
trigger += `:has(.info-list .discount.em:contains("${discount}"))`;
|
||||
}
|
||||
if (priceNoDiscount) {
|
||||
trigger += `:has(.info-list:contains("${priceNoDiscount}"))`;
|
||||
}
|
||||
if (attributeLine) {
|
||||
trigger += `:has(.attribute-line:contains("${attributeLine}"))`;
|
||||
}
|
||||
const args = JSON.stringify(arguments[0]);
|
||||
return [
|
||||
{
|
||||
content: `Check orderline with attributes: ${args}`,
|
||||
trigger,
|
||||
run: typeof run === "string" ? run : () => run(trigger),
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @param {LineOptions} options
|
||||
* @returns {import("@web_tour/tour_service").TourStep}
|
||||
*/
|
||||
export function doesNotHaveLine(options = {}) {
|
||||
const step = hasLine(options)[0];
|
||||
return [{ ...step, trigger: negate(step.trigger) }];
|
||||
}
|
||||
|
||||
// TODO: there are instances where we have no selected orderline. Fix those instances
|
||||
|
||||
export function hasTotal(amount) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
content: `order total amount is '${amount}'`,
|
||||
trigger: `.product-screen .order-summary .total:contains("${amount}")`,
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: `order total amount is '${amount}'`,
|
||||
trigger: `.product-screen .order-summary .total:contains("${amount}"):not(:visible)`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function hasSubtotal(amount) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
content: `order total amount is '${amount}'`,
|
||||
trigger: `.product-screen .order-summary .subtotal:contains("${amount}")`,
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: `order total amount is '${amount}'`,
|
||||
trigger: `.product-screen .order-summary .subtotal:contains("${amount}"):not(:visible)`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function hasTax(amount) {
|
||||
return {
|
||||
content: `order total tax is '${amount}'`,
|
||||
trigger: `.order-summary .tax:contains("${amount}")`,
|
||||
};
|
||||
}
|
||||
export function hasInternalNote(note) {
|
||||
return [
|
||||
{
|
||||
content: `Order internal note is '${note}'`,
|
||||
trigger: `.order-container .internal-note-container span div:contains("${note}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function hasCustomerNote(note) {
|
||||
return [
|
||||
{
|
||||
content: `Order customer note is '${note}'`,
|
||||
trigger: `.order-container .customer-note div:contains("${note}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function hasNoTax() {
|
||||
return {
|
||||
content: "order has not tax",
|
||||
trigger: negate(".tax-info"),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export function has(item, { run = () => {} } = {}) {
|
||||
return [
|
||||
{
|
||||
content: `selection popup has '${item}'`,
|
||||
trigger: `.selection-item:contains("${item}")`,
|
||||
run,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export function inputText(val) {
|
||||
return {
|
||||
content: `input text '${val}'`,
|
||||
trigger: `.modal:not(.o_inactive_modal) textarea`,
|
||||
run: `edit ${val}`,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/* global posmodel */
|
||||
|
||||
import { simulateBarCode } from "@barcodes/../tests/legacy/helpers";
|
||||
|
||||
export function negate(selector, parent = "body") {
|
||||
return `${parent}:not(:has(${selector}))`;
|
||||
}
|
||||
export function run(run, content = "run function", expectUnloadPage = false) {
|
||||
return { content, trigger: "body", run, expectUnloadPage };
|
||||
}
|
||||
export function scan_barcode(barcode) {
|
||||
return [
|
||||
{
|
||||
content: `PoS model scan barcode '${barcode}'`,
|
||||
trigger: "body", // The element here does not really matter as long as it is present
|
||||
run: () => {
|
||||
simulateBarCode([...barcode, "Enter"]);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
export function negateStep(step) {
|
||||
return {
|
||||
...step,
|
||||
content: `Check that: ---${step.content}--- is not true`,
|
||||
trigger: negate(step.trigger),
|
||||
};
|
||||
}
|
||||
export function refresh() {
|
||||
return run(
|
||||
async () => {
|
||||
await new Promise((resolve) => {
|
||||
const checkTransaction = () => {
|
||||
const activeTransactions = posmodel.data.indexedDB.activeTransactions;
|
||||
if (activeTransactions.size === 0) {
|
||||
window.location.reload();
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkTransaction, 100);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
checkTransaction();
|
||||
}, 305);
|
||||
setTimeout(() => {
|
||||
const activeTx = posmodel.data.indexedDB.activeTransactions;
|
||||
const storeNames = Array.from(activeTx).flatMap((tx) =>
|
||||
Array.from(tx.objectStoreName)
|
||||
);
|
||||
const uniqueStores = [...new Set(storeNames)].join(", ");
|
||||
throw new Error(
|
||||
`Timeout waiting indexedDB for transactions to finish. Stores open: [${uniqueStores}]`
|
||||
);
|
||||
}, 2000);
|
||||
});
|
||||
},
|
||||
"refresh page",
|
||||
true
|
||||
);
|
||||
}
|
||||
export function elementDoesNotExist(selector) {
|
||||
return {
|
||||
content: `Check that element "${selector}" don't exist.`,
|
||||
trigger: negate(selector),
|
||||
};
|
||||
}
|
||||
|
||||
export function assertCurrentOrderDirty(dirty = true) {
|
||||
return {
|
||||
trigger: "body",
|
||||
run() {
|
||||
if (posmodel.getOrder().isDirty() !== dirty) {
|
||||
throw new Error("Order should be " + (dirty ? "dirty" : "not dirty"));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_03_pos_with_lots", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Monitor Stand"),
|
||||
ProductScreen.enterLotNumber("1", "lot"),
|
||||
ProductScreen.selectedOrderlineHas("Monitor Stand", "1"),
|
||||
ProductScreen.clickReview(),
|
||||
{ ...ProductScreen.clickLine("Monitor Stand")[0], isActive: ["mobile"] },
|
||||
Numpad.click("2"),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.totalAmountIs("6.38"),
|
||||
ProductScreen.clickDisplayedProduct("Monitor Stand"),
|
||||
ProductScreen.enterLotNumber("2", "lot"),
|
||||
ProductScreen.clickReview(),
|
||||
{ ...ProductScreen.clickLine("Monitor Stand")[0], isActive: ["mobile"] },
|
||||
Numpad.click("3"),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.totalAmountIs("15.95"),
|
||||
ProductScreen.clickPriceList("min_quantity ordering"),
|
||||
ProductScreen.totalAmountIs("5.00"),
|
||||
ProductScreen.clickReview(),
|
||||
{ ...ProductScreen.clickLine("Monitor Stand")[0], isActive: ["mobile"] },
|
||||
Numpad.click("⌫"),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.totalAmountIs("6.38"),
|
||||
ProductScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_lot_tracking_without_lot_creation", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Monitor Stand"),
|
||||
ProductScreen.totalAmountIs("3.19"),
|
||||
ProductScreen.clickDisplayedProduct("Monitor Stand"),
|
||||
ProductScreen.totalAmountIs("6.38"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import { inLeftSide, waitForLoading } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
|
||||
registry.category("web_tour.tours").add("pos_basic_order_02_decimal_order_quantity", {
|
||||
steps: () =>
|
||||
[
|
||||
waitForLoading(),
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "1"),
|
||||
inLeftSide([
|
||||
{ ...ProductScreen.clickLine("Desk Organizer")[0], isActive: ["mobile"] },
|
||||
Numpad.click("."),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Organizer", "0"),
|
||||
Numpad.click("9"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Organizer", "0.9"),
|
||||
Numpad.click("9"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Organizer", "0.99"),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash", true, { amount: "5.05" }),
|
||||
ProductScreen.finishOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("pos_basic_order_03_tax_position", {
|
||||
steps: () =>
|
||||
[
|
||||
waitForLoading(),
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Letter Tray", true, "1"),
|
||||
inLeftSide(...Order.hasTotal("5.28")),
|
||||
ProductScreen.clickFiscalPosition("FP-POS-2M", true),
|
||||
inLeftSide(...Order.hasTotal("5.52")),
|
||||
ProductScreen.closePos(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { scan_barcode } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("BarcodeScanningTour", {
|
||||
steps: () =>
|
||||
[
|
||||
// The following step is to make sure that the Chrome widget initialization ends
|
||||
// If we try to use the barcode parser before its initiation, we will have
|
||||
// some inconsistent JS errors:
|
||||
// TypeError: Cannot read properties of undefined (reading 'parse_barcode')
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Add a product with its barcode
|
||||
scan_barcode("0123456789"),
|
||||
ProductScreen.selectedOrderlineHas("Monitor Stand"),
|
||||
scan_barcode("0123456789"),
|
||||
ProductScreen.selectedOrderlineHas("Monitor Stand", 2),
|
||||
|
||||
// Test "Prices product" EAN-13 `23.....{NNNDD}` barcode pattern
|
||||
scan_barcode("2305000000004"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", 1, "0.00"),
|
||||
scan_barcode("2305000123451"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", 1, "123.45"),
|
||||
|
||||
// Test "Weighted product" EAN-13 `21.....{NNDDD}` barcode pattern
|
||||
scan_barcode("2100005000000"),
|
||||
ProductScreen.selectedOrderlineHas("Wall Shelf Unit", 0, "0.00"),
|
||||
scan_barcode("2100005080002"),
|
||||
ProductScreen.selectedOrderlineHas("Wall Shelf Unit", 8),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("BarcodeScanningProductPackagingTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Add the product with its barcode
|
||||
scan_barcode("12345601"),
|
||||
ProductScreen.selectedOrderlineHas("Packaging Product", 1),
|
||||
scan_barcode("12345601"),
|
||||
ProductScreen.selectedOrderlineHas("Packaging Product", 2),
|
||||
|
||||
// Add the product packaging with its barcode
|
||||
scan_barcode("12345610"),
|
||||
ProductScreen.selectedOrderlineHas("Packaging Product", 12),
|
||||
scan_barcode("12345610"),
|
||||
ProductScreen.selectedOrderlineHas("Packaging Product", 22),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("GS1BarcodeScanningTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Add the Product 1 with GS1 barcode
|
||||
scan_barcode("0108431673020125100000001"),
|
||||
ProductScreen.selectedOrderlineHas("Product 1"),
|
||||
scan_barcode("0108431673020125100000001"),
|
||||
ProductScreen.selectedOrderlineHas("Product 1", 2),
|
||||
|
||||
// Add the product 1 with GS1 barcode and quantity
|
||||
scan_barcode("0108431673020125305"),
|
||||
ProductScreen.selectedOrderlineHas("Product 1", 7),
|
||||
scan_barcode("01084316730201253010"),
|
||||
ProductScreen.selectedOrderlineHas("Product 1", 17),
|
||||
|
||||
// Add the Product 2 with normal barcode
|
||||
scan_barcode("08431673020126"),
|
||||
ProductScreen.selectedOrderlineHas("Product 2"),
|
||||
scan_barcode("08431673020126"),
|
||||
ProductScreen.selectedOrderlineHas("Product 2", 2),
|
||||
|
||||
// Add the Product 3 with normal barcode
|
||||
scan_barcode("3760171283370"),
|
||||
ProductScreen.selectedOrderlineHas("Product 3"),
|
||||
scan_barcode("3760171283370"),
|
||||
ProductScreen.selectedOrderlineHas("Product 3", 2),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("BarcodeScanPartnerTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// scan the customer barcode
|
||||
scan_barcode("0421234567890"),
|
||||
ProductScreen.customerIsSelected("John Doe"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_quantity_package_of_non_basic_unit", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
scan_barcode("555555"),
|
||||
ProductScreen.selectedOrderlineHas("Cord", 12),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as CashMoveList from "@point_of_sale/../tests/pos/tours/utils/cash_move_list_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_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 Utils from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import { refresh } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import * as PartnerList from "@point_of_sale/../tests/pos/tours/utils/partner_list_util";
|
||||
|
||||
registry.category("web_tour.tours").add("ChromeTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.clickMenuButton(),
|
||||
Chrome.clickMenuDropdownOption("Cash In/Out"),
|
||||
Chrome.fillTextArea(".cash-reason", "MOBT"),
|
||||
Dialog.confirm(),
|
||||
Chrome.clickMenuButton(),
|
||||
|
||||
// Order 1 is at Product Screen
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "2", "2.0"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.checkStatus("001", "Ongoing"),
|
||||
|
||||
// Order 2 is at Payment Screen
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.addOrderline("Monitor Stand", "3", "4", "12.0"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.isShown(),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.checkStatus("002", "Payment"),
|
||||
|
||||
// Order 3 is at Receipt Screen
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "5", "6", "30.0"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.0" }),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.checkStatus("003", "Receipt"),
|
||||
|
||||
// Select order 1, should be at Product Screen
|
||||
TicketScreen.selectOrder("001"),
|
||||
TicketScreen.loadSelectedOrder(),
|
||||
ProductScreen.productIsDisplayed("Desk Pad"),
|
||||
inLeftSide([
|
||||
...ProductScreen.clickLine("Desk Pad"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Pad", "1", "2.0"),
|
||||
]),
|
||||
|
||||
// Select order 2, should be at Payment Screen
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectOrder("002"),
|
||||
TicketScreen.loadSelectedOrder(),
|
||||
PaymentScreen.emptyPaymentlines("12.0"),
|
||||
PaymentScreen.validateButtonIsHighlighted(false),
|
||||
|
||||
// Select order 3, should be at Receipt Screen
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectOrder("003"),
|
||||
TicketScreen.loadSelectedOrder(),
|
||||
ReceiptScreen.totalAmountContains("30.0"),
|
||||
|
||||
// Pay order 1, with change
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
TicketScreen.loadSelectedOrder(),
|
||||
ProductScreen.isShown(),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.enterPaymentLineAmount("Cash", "20", true, { change: "18.0" }),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.totalAmountContains("2.0"),
|
||||
|
||||
// Order 1 now should have Receipt status
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.checkStatus("001", "Receipt"),
|
||||
|
||||
// Select order 3, should still be at Receipt Screen
|
||||
// and the total amount doesn't change.
|
||||
TicketScreen.selectOrder("003"),
|
||||
TicketScreen.loadSelectedOrder(),
|
||||
ReceiptScreen.totalAmountContains("30.0"),
|
||||
|
||||
// click next screen on order 3
|
||||
// then delete the new empty order
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.orderIsEmpty(),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.deleteOrder("004"),
|
||||
|
||||
// After deleting order 1 above, order 2 became
|
||||
// the 1st-row order and it has payment status
|
||||
TicketScreen.nthRowContains(1, "Payment"),
|
||||
TicketScreen.deleteOrder("002"),
|
||||
Dialog.confirm(),
|
||||
Chrome.clickRegister(),
|
||||
|
||||
// Invoice an order
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "5", "6"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
{ trigger: ".receipt-screen .pos-config-name:contains(Shop)" },
|
||||
|
||||
// Cancelling a floating order should remove it from the floating orders list.
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
Chrome.hasFloatingOrder("004"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("OrderModificationAfterValidationError", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product", true, "1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.0" }),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
// Dialog showing the error
|
||||
Dialog.confirm(),
|
||||
|
||||
PaymentScreen.clickBack(),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.isShown(),
|
||||
|
||||
// Allow order changes after the error
|
||||
ProductScreen.clickDisplayedProduct("Test Product", true, "2"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_tracking_number_closing_session", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "1.0"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.isShown(),
|
||||
Chrome.clickMenuOption("Close Register"),
|
||||
{
|
||||
content: `Select button close register`,
|
||||
trigger: `button:contains(close register)`,
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad", true, "1.0"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.enterPaymentLineAmount("Bank", "20"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_reload_page_before_payment_with_customer_account", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "1.0"),
|
||||
refresh(),
|
||||
ProductScreen.productIsDisplayed("Desk Organizer"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Customer Account"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.isShown(),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "1.0"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Customer Account"),
|
||||
PaymentScreen.clickValidate(),
|
||||
Dialog.cancel(),
|
||||
PaymentScreen.clickValidate(),
|
||||
Dialog.confirm("Ok"),
|
||||
PaymentScreen.clickCustomer("Partner Test 1"),
|
||||
PaymentScreen.clickValidate(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_cash_in_out", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.freezeDateTime(1749965940000),
|
||||
Chrome.doCashMove("10", "MOBT in"),
|
||||
Chrome.doCashMove("5", "MOBT out"),
|
||||
Chrome.clickMenuOption("Close Register"),
|
||||
Utils.selectButton("Cash In/Out"),
|
||||
Utils.selectButton("Details"),
|
||||
CashMoveList.checkNumberOfRows(2),
|
||||
CashMoveList.checkCashMoveShown("10"),
|
||||
CashMoveList.checkCashMoveShown("5"),
|
||||
CashMoveList.checkCashMoveDateTime(),
|
||||
CashMoveList.deleteCashMove("10"),
|
||||
CashMoveList.checkNumberOfRows(1),
|
||||
CashMoveList.checkCashMoveShown("5"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_zero_decimal_places_currency", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product", true, "1.00"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
ReceiptScreen.totalAmountContains("100"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("SessionStatisticsDisplay", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.enterOpeningAmount("100.00"),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Desk Pad", "5", "5"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.isShown(),
|
||||
ProductScreen.addOrderline("Monitor Stand", "2", "10"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.isShown(),
|
||||
Chrome.clickMenuOption("Backend", { expectUnloadPage: true }),
|
||||
{
|
||||
trigger: `[name=opening_cash]:contains(100.00)`,
|
||||
},
|
||||
{
|
||||
trigger: `[name=paid_orders]:contains(45.00 (2 orders))`,
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_click_all_orders_keep_customer", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
PartnerList.clickPartnerOptions("Partner Test 1"),
|
||||
{
|
||||
isActive: ["auto"],
|
||||
trigger: "body .dropdown-item:contains('All Orders')",
|
||||
content: "Check the popover opened",
|
||||
run: "click",
|
||||
},
|
||||
Chrome.clickRegister(),
|
||||
ProductScreen.isShown(),
|
||||
{
|
||||
content: "customer is selected",
|
||||
trigger: ".product-screen .set-partner:contains('Partner Test 1')",
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_ctrl_number_ignored", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1", "6", "6.0"),
|
||||
{
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
window.dispatchEvent(new KeyboardEvent("keyup", { key: "5", ctrlKey: true }));
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: "body",
|
||||
run: () =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, 300); // wait 300ms so NumberBuffer timeout runs
|
||||
}),
|
||||
},
|
||||
inLeftSide([
|
||||
{ ...ProductScreen.clickLine("Whiteboard Pen")[0], isActive: ["mobile"] },
|
||||
...ProductScreen.selectedOrderlineHasDirect("Whiteboard Pen", "1", "6.0"),
|
||||
]),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_set_opening_note_without_cash_method", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
{
|
||||
content: "Add Opening Notes",
|
||||
trigger: ".opening-notes",
|
||||
run: "edit Opening Notes",
|
||||
},
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1", "6", "6.0"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("chrome_without_cash_move_permission", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.clickMenuButton(),
|
||||
Chrome.isCashMoveButtonHidden(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("customer_display_shows_qr_popup", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.waitForMenuButtons(),
|
||||
Chrome.clickMenuButton(),
|
||||
Chrome.waitForMenuOptionsToOpen(),
|
||||
Chrome.ClickOnCustomerDisplayButton(),
|
||||
Chrome.CustomerDisplayHasThisDeviceButton(),
|
||||
Chrome.CustomerDisplayHasQRButton(),
|
||||
Chrome.ClickCustomerDisplayQRButton(),
|
||||
Chrome.CustomerDisplayQRIsDisplayed(),
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: "Check that the Customer display url is valid",
|
||||
trigger: ".o-overlay-item .modal .modal-body .small a",
|
||||
run: function (el) {
|
||||
const url = el.anchor.href;
|
||||
if (!url || url.includes("undefined")) {
|
||||
throw new Error(
|
||||
`Invalid customer display URL (contains undefined): ${url}`
|
||||
);
|
||||
}
|
||||
try {
|
||||
new URL(url);
|
||||
} catch {
|
||||
throw new Error(`Invalid customer display URL: ${url}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: "Check that the Qr popup has close button",
|
||||
trigger: ".o-overlay-item .modal .modal-body button.button.btn-secondary",
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_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";
|
||||
|
||||
registry.category("web_tour.tours").add("PoSFakeTourSimpleOrder", {
|
||||
steps: () =>
|
||||
[
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
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 ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as FeedbackScreen from "@point_of_sale/../tests/pos/tours/utils/feedback_screen_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_automatic_receipt_printing", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
FeedbackScreen.isShown(),
|
||||
FeedbackScreen.clickScreen(),
|
||||
ProductScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
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 { registry } from "@web/core/registry";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
|
||||
registry.category("web_tour.tours").add("FixedTaxNegativeQty", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Zero Amount Product", true, "1", "1.0"),
|
||||
inLeftSide([
|
||||
{
|
||||
...ProductScreen.clickLine("Zero Amount Product", "1")[0],
|
||||
isActive: ["mobile"],
|
||||
},
|
||||
...["+/-"].map(Numpad.click),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Zero Amount Product", "-1", "-1.0"),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.00" }),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_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 { GenericHooks } from "@point_of_sale/../tests/pos/tours/utils/generic_hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
//This tour is meant to be run on all localizations
|
||||
registry.category("web_tour.tours").add("generic_localization_tour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS().map((step) => ({ ...step, timeout: 20000 })),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA Generic Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.clickDisplayedProduct("Wall Shelf Unit"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
GenericHooks.afterValidateHook(),
|
||||
{
|
||||
timeout: 20000,
|
||||
content: "receipt screen is shown",
|
||||
trigger: ".pos .receipt-screen",
|
||||
},
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.isShown(),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as OptionalProduct from "@point_of_sale/../tests/pos/tours/utils/optional_product_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { scan_barcode } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("test_optional_product", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Select a product without configurable options
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad", false),
|
||||
Dialog.is({ title: "Optional Products" }),
|
||||
// Cancel the popup; no optional product should be added to the cart
|
||||
Dialog.cancel(),
|
||||
ProductScreen.selectedOrderlineHas("Desk Pad", "1.0", "1.98"),
|
||||
|
||||
// Add a product with optional products
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad", false),
|
||||
Dialog.is({ title: "Optional Products" }),
|
||||
// Check image of optional product
|
||||
OptionalProduct.checkImage("Small Shelf", true),
|
||||
// Add a specific optional product
|
||||
OptionalProduct.addOptionalProduct("Small Shelf", 5),
|
||||
ProductScreen.selectedOrderlineHas("Small Shelf", "5.0"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Letter Tray"),
|
||||
// Add an optional product with configurations
|
||||
OptionalProduct.addOptionalProduct("Configurable Chair", 5, true),
|
||||
// Verify the configurable product is added with correct attributes and quantity
|
||||
ProductScreen.selectedOrderlineHas(
|
||||
"Configurable Chair",
|
||||
"5.0",
|
||||
"50.0",
|
||||
"Blue, Metal, wool"
|
||||
),
|
||||
|
||||
// Scan a product with optional products
|
||||
scan_barcode("lettertray"),
|
||||
Dialog.is({ title: "Optional Products" }),
|
||||
// Add an optional product
|
||||
OptionalProduct.addOptionalProduct("Configurable Chair", 2, true),
|
||||
// Verify the configurable product is added with correct attributes and quantity
|
||||
ProductScreen.selectedOrderlineHas(
|
||||
"Configurable Chair",
|
||||
"7.0",
|
||||
"70.0",
|
||||
"Blue, Metal, wool"
|
||||
),
|
||||
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_optional_product_image_not_display", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad"),
|
||||
OptionalProduct.checkImage("Small Shelf", false),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
/* global posmodel */
|
||||
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as OfflineUtil from "@point_of_sale/../tests/generic_helpers/offline_util";
|
||||
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import * as NumberPopup from "@point_of_sale/../tests/generic_helpers/number_popup_util";
|
||||
import { inLeftSide } from "./utils/common";
|
||||
|
||||
registry.category("web_tour.tours").add("PaymentScreenTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
OfflineUtil.setOfflineMode(),
|
||||
ProductScreen.addOrderline("Letter Tray", "10"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.emptyPaymentlines("52.8"),
|
||||
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.enterPaymentLineAmount("Cash", "11", true, {
|
||||
amount: "11.00",
|
||||
remaining: "41.8",
|
||||
}),
|
||||
PaymentScreen.validateButtonIsHighlighted(false),
|
||||
// remove the selected paymentline with multiple backspace presses
|
||||
PaymentScreen.clickNumpad("⌫ ⌫"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Cash", "0"),
|
||||
PaymentScreen.selectedPaymentlineHas("Cash", "0.00"),
|
||||
PaymentScreen.clickPaymentlineDelButton("Cash", "0", true),
|
||||
PaymentScreen.emptyPaymentlines("52.8"),
|
||||
|
||||
// Pay with bank, the selected line should have full amount
|
||||
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.0" }),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
// remove the line using the delete button
|
||||
PaymentScreen.clickPaymentlineDelButton("Bank", "52.8"),
|
||||
|
||||
// Use +10 and +50 to increment the amount of the paymentline
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickNumpad("⌫"),
|
||||
PaymentScreen.clickNumpad("+10"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Cash", "10"),
|
||||
PaymentScreen.remainingIs("42.8"),
|
||||
PaymentScreen.validateButtonIsHighlighted(false),
|
||||
PaymentScreen.clickNumpad("5"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Cash", "105"),
|
||||
PaymentScreen.changeIs("52.2"),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickNumpad("+50"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Cash", "155"),
|
||||
PaymentScreen.changeIs("102.2"),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickPaymentlineDelButton("Cash", "155.0"),
|
||||
|
||||
// Multiple paymentlines
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickNumpad("1"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Cash", "1"),
|
||||
PaymentScreen.remainingIs("51.8"),
|
||||
PaymentScreen.validateButtonIsHighlighted(false),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Bank", "5"),
|
||||
PaymentScreen.clickNumpad("5"),
|
||||
PaymentScreen.remainingIs("46.8"),
|
||||
PaymentScreen.validateButtonIsHighlighted(false),
|
||||
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.0" }),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
OfflineUtil.setOnlineMode(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PaymentScreenTour2", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Letter Tray", "1", "10"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
// check that ship later button is present
|
||||
{ trigger: ".payment-buttons button:contains('Ship Later')" },
|
||||
|
||||
PaymentScreen.enterPaymentLineAmount("Bank", "99"),
|
||||
// trying to put 99 as an amount should throw an error. We thus confirm the dialog.
|
||||
Dialog.confirm(),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PaymentScreenRoundingUp", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Product Test", "1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("1.96"),
|
||||
PaymentScreen.clickPaymentMethod("Cash", true, { remaining: "0.0", amount: "2.00" }),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.selectOrder("001"),
|
||||
inLeftSide([
|
||||
...Order.hasLine({ productName: "Product Test", withClass: ".selected" }),
|
||||
Numpad.click("1"),
|
||||
]),
|
||||
TicketScreen.confirmRefund(),
|
||||
|
||||
// To get negative of existing quantity just send -
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.totalIs("-1.96"),
|
||||
PaymentScreen.clickPaymentMethod("Cash", true, { remaining: "0.0", amount: "-2.00" }),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PaymentScreenRoundingDown", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Product Test", "1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("1.98"),
|
||||
PaymentScreen.clickPaymentMethod("Cash", true, { remaining: "0.0", amount: "1.95" }),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.selectOrder("001"),
|
||||
inLeftSide([
|
||||
...Order.hasLine({ productName: "Product Test", withClass: ".selected" }),
|
||||
Numpad.click("1"),
|
||||
]),
|
||||
TicketScreen.confirmRefund(),
|
||||
|
||||
// To get negative of existing quantity just send -
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.totalIs("-1.98"),
|
||||
PaymentScreen.clickPaymentMethod("Cash", true, { remaining: "0.0", amount: "-1.95" }),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PaymentScreenTotalDueWithOverPayment", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.addOrderline("Product Test", "1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("1.98"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.enterPaymentLineAmount("Cash", "5", true, {
|
||||
change: "3",
|
||||
}),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("InvoiceShipLaterAccessRight", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.confirmOpeningPopup(),
|
||||
ProductScreen.clickHomeCategory(),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Acme Corporation"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickShipLaterButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PaymentScreenInvoiceOrder", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Product Test", "1"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_pos_large_amount_confirmation_dialog", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Overpay Test Product"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.enterPaymentLineAmount("Cash", "1500"),
|
||||
PaymentScreen.clickValidate(),
|
||||
{
|
||||
trigger: ".modal .modal-footer .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_add_money_button_with_different_decimal_separator", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickNumpad("+50"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Bank", "53,20"),
|
||||
PaymentScreen.changeIs("50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_payment_screen_tip_scenario", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Letter Tray", "1", "10"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
{
|
||||
content: "Switch localization to comma",
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
posmodel.numberBuffer.localization.decimalPoint = ",";
|
||||
posmodel.numberBuffer.localization.thousandsSep = ".";
|
||||
posmodel.numberBuffer._setUp();
|
||||
},
|
||||
},
|
||||
|
||||
PaymentScreen.clickTipButton(),
|
||||
NumberPopup.enterValue("1,50"),
|
||||
NumberPopup.isShown("1,50"),
|
||||
Dialog.confirm(),
|
||||
PaymentScreen.totalIs("12,50"),
|
||||
|
||||
{
|
||||
content: "Switch localization back to dot",
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
posmodel.numberBuffer.localization.decimalPoint = ".";
|
||||
posmodel.numberBuffer.localization.thousandsSep = ",";
|
||||
posmodel.numberBuffer._setUp();
|
||||
},
|
||||
},
|
||||
|
||||
PaymentScreen.clickTipButton(),
|
||||
NumberPopup.enterValue("2.5"),
|
||||
NumberPopup.isShown("2.5"),
|
||||
Dialog.confirm(),
|
||||
PaymentScreen.totalIs("13.50"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_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 TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry
|
||||
.category("web_tour.tours")
|
||||
.add("test_cash_rounding_halfup_biggest_tax_not_only_round_cash_method", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Order.
|
||||
ProductScreen.addOrderline("random_product", "1"),
|
||||
ProductScreen.checkTaxAmount("2.03"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("15.70"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAA"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("15.70"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("15.70"),
|
||||
ReceiptScreen.receiptToPayAmountIsNotThere(),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// Refund.
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Active"),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.selectOrder("0001"),
|
||||
TicketScreen.confirmRefund(),
|
||||
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
|
||||
ProductScreen.checkTaxAmount("-2.03"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("-15.70"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("-15.70"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("-15.70"),
|
||||
ReceiptScreen.receiptToPayAmountIsNotThere(),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry
|
||||
.category("web_tour.tours")
|
||||
.add("test_cash_rounding_halfup_biggest_tax_not_only_round_cash_method_pay_by_bank_and_cash", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Order.
|
||||
ProductScreen.addOrderline("random_product", "1"),
|
||||
ProductScreen.checkTaxAmount("2.03"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("15.70"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAA"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("15.70"),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickNumpad("0 . 6 7"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Bank", "0.67"),
|
||||
PaymentScreen.remainingIs("15.03"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("15.70"),
|
||||
ReceiptScreen.receiptRoundingAmountIs("0.02"),
|
||||
ReceiptScreen.receiptToPayAmountIs("15.72"),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// Refund.
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Active"),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.selectOrder("0001"),
|
||||
TicketScreen.confirmRefund(),
|
||||
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
|
||||
ProductScreen.checkTaxAmount("-2.03"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("-15.70"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("-15.70"),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickNumpad("0 . 6 7 +/-"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Bank", "-0.67"),
|
||||
PaymentScreen.remainingIs("-15.03"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("-15.70"),
|
||||
ReceiptScreen.receiptRoundingAmountIs("-0.02"),
|
||||
ReceiptScreen.receiptToPayAmountIs("-15.72"),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry
|
||||
.category("web_tour.tours")
|
||||
.add("test_cash_rounding_halfup_biggest_tax_only_round_cash_method", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Order.
|
||||
ProductScreen.addOrderline("random_product", "1"),
|
||||
ProductScreen.checkTaxAmount("2.05"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("15.72"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAA"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("15.72"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("15.72"),
|
||||
ReceiptScreen.receiptRoundingAmountIs("-0.02"),
|
||||
ReceiptScreen.receiptToPayAmountIs("15.70"),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// Refund.
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Active"),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.selectOrder("0001"),
|
||||
TicketScreen.confirmRefund(),
|
||||
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
|
||||
ProductScreen.checkTaxAmount("-2.05"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("-15.72"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("-15.72"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("-15.72"),
|
||||
ReceiptScreen.receiptRoundingAmountIs("0.02"),
|
||||
ReceiptScreen.receiptToPayAmountIs("-15.70"),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry
|
||||
.category("web_tour.tours")
|
||||
.add("test_cash_rounding_halfup_biggest_tax_only_round_cash_method_pay_by_bank_and_cash", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Order.
|
||||
ProductScreen.addOrderline("random_product", "1"),
|
||||
ProductScreen.checkTaxAmount("2.05"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("15.72"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAA"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("15.72"),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickNumpad("0 . 6 8"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Bank", "0.68"),
|
||||
PaymentScreen.remainingIs("15.04"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("15.72"),
|
||||
ReceiptScreen.receiptRoundingAmountIs("0.01"),
|
||||
ReceiptScreen.receiptToPayAmountIs("15.73"),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// Refund.
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Active"),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.selectOrder("0001"),
|
||||
TicketScreen.confirmRefund(),
|
||||
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
|
||||
ProductScreen.checkTaxAmount("-2.05"),
|
||||
ProductScreen.checkRoundingAmountIsNotThere(),
|
||||
ProductScreen.checkTotalAmount("-15.72"),
|
||||
ProductScreen.clickPayButton(),
|
||||
|
||||
PaymentScreen.totalIs("-15.72"),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickNumpad("0 . 6 8 +/-"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Bank", "-0.68"),
|
||||
PaymentScreen.remainingIs("-15.04"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.remainingIs("0.0"),
|
||||
PaymentScreen.clickValidate(),
|
||||
|
||||
ReceiptScreen.receiptAmountTotalIs("-15.72"),
|
||||
ReceiptScreen.receiptRoundingAmountIs("-0.01"),
|
||||
ReceiptScreen.receiptToPayAmountIs("-15.73"),
|
||||
ReceiptScreen.receiptChangeAmountIsNotThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
|
||||
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
|
||||
import * as combo from "@point_of_sale/../tests/pos/tours/utils/combo_popup_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import { scan_barcode } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import * as Utils from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("ProductComboPriceTaxIncludedTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
...ProductScreen.clickDisplayedProduct("Sofa Combo"),
|
||||
combo.select("Combo Product Sofa (L, red)"),
|
||||
Dialog.confirm(),
|
||||
inLeftSide([
|
||||
...Order.hasLine({
|
||||
productName: "Combo product Sofa",
|
||||
run: "click",
|
||||
quantity: "1",
|
||||
attributeLine: "L, red",
|
||||
}),
|
||||
Numpad.click("⌫"),
|
||||
Numpad.click("⌫"),
|
||||
...Order.doesNotHaveLine(),
|
||||
]),
|
||||
scan_barcode("SuperCombo"),
|
||||
combo.select("Combo Product 3"),
|
||||
combo.isConfirmationButtonDisabled(),
|
||||
combo.select("Combo Product 9"),
|
||||
// Check Product Configurator is open
|
||||
Dialog.is("Attribute selection"),
|
||||
{
|
||||
content: "dialog discard",
|
||||
trigger: ".modal-footer .btn:text(Add) + .btn:text(Discard)",
|
||||
run: "click",
|
||||
},
|
||||
combo.select("Combo Product 5"),
|
||||
combo.select("Combo Product 7"),
|
||||
combo.isSelected("Combo Product 7"),
|
||||
combo.select("Combo Product 8"),
|
||||
combo.isSelected("Combo Product 8"),
|
||||
combo.isNotSelected("Combo Product 7"),
|
||||
Dialog.confirm(),
|
||||
inLeftSide([
|
||||
...ProductScreen.selectedOrderlineHasDirect("Office Combo", "1", "62.1"),
|
||||
...ProductScreen.clickLine("Combo Product 3"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Combo Product 3", "1"),
|
||||
...ProductScreen.clickLine("Combo Product 5"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Combo Product 5", "1"),
|
||||
...ProductScreen.clickLine("Combo Product 8"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Combo Product 8", "1"),
|
||||
]),
|
||||
// check that you can select a customer which triggers a recomputation of the price
|
||||
...ProductScreen.clickPartnerButton(),
|
||||
...ProductScreen.clickCustomer("Partner Test 1"),
|
||||
|
||||
// check that you can change the quantity of a combo product
|
||||
inLeftSide([
|
||||
...ProductScreen.clickLine("Combo Product 3"),
|
||||
Numpad.click("2"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Combo Product 3", "2"),
|
||||
...ProductScreen.orderLineHas("Combo Product 5", "2"),
|
||||
...ProductScreen.orderLineHas("Combo Product 8", "2"),
|
||||
...ProductScreen.orderLineHas("Office Combo", "2", "124.2"),
|
||||
]),
|
||||
|
||||
// check that removing a combo product removes all the combo products
|
||||
inLeftSide([
|
||||
{
|
||||
...ProductScreen.clickLine("Combo Product 3", "2")[0],
|
||||
isActive: ["mobile"],
|
||||
},
|
||||
Numpad.click("⌫"),
|
||||
Numpad.click("⌫"),
|
||||
...Order.doesNotHaveLine(),
|
||||
]),
|
||||
|
||||
...ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 3"),
|
||||
combo.select("Combo Product 5"),
|
||||
combo.select("Combo Product 8"),
|
||||
Dialog.confirm(),
|
||||
...ProductScreen.totalAmountIs("62.10"),
|
||||
...ProductScreen.clickPayButton(),
|
||||
...PaymentScreen.clickPaymentMethod("Bank"),
|
||||
...PaymentScreen.clickValidate(),
|
||||
...ReceiptScreen.isShown(),
|
||||
...ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// another order but won't be sent to the backend
|
||||
...ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 2"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
{
|
||||
content: "The 'Combo Product 6' card should not display a quantity.",
|
||||
trigger:
|
||||
"article.product .product-content:has(.product-name:contains('Combo Product 6')):not(:has(.product-cart-qty))",
|
||||
},
|
||||
...ProductScreen.totalAmountIs("59.17"),
|
||||
...inLeftSide(Order.hasTax("10.56")),
|
||||
// the split screen is tested in `pos_restaurant`
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ProductComboPriceCheckTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Combo"),
|
||||
inLeftSide([
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Combo", "1", "7.00"),
|
||||
...ProductScreen.orderLineHas("Desk Organizer", "1"),
|
||||
...ProductScreen.orderLineHas("Desk Pad", "1"),
|
||||
...ProductScreen.orderLineHas("Whiteboard Pen", "1"),
|
||||
]),
|
||||
ProductScreen.totalAmountIs("7.00"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ProductComboChangeFP", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
ProductScreen.checkProductExtraPrice("Combo Product 3", "2"),
|
||||
combo.select("Combo Product 2"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
|
||||
inLeftSide([...ProductScreen.orderLineHas("Office Combo", "1", "50.00")]),
|
||||
ProductScreen.totalAmountIs("50.00"),
|
||||
inLeftSide(Order.hasTax("4.55")),
|
||||
|
||||
// Test than changing the fp, doesn't change the price of the combo
|
||||
ProductScreen.clickFiscalPosition("test fp"),
|
||||
inLeftSide([...ProductScreen.orderLineHas("Office Combo", "1", "50.00")]),
|
||||
ProductScreen.totalAmountIs("50.00"),
|
||||
inLeftSide(Order.hasTax("2.38")),
|
||||
ProductScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_combo_refund_different_qty", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Desk accessories combo
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 3"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.checkProductQty("Combo Product 4", "2"),
|
||||
combo.select("Combo Product 6"),
|
||||
|
||||
Dialog.confirm(),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
// First refund order
|
||||
ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.toRefundLineContains("Office Combo", "To Refund: 1.00"),
|
||||
TicketScreen.toRefundLineContains("Combo Product 4", "To Refund: 2.00"),
|
||||
TicketScreen.toRefundLineContains("Combo Product 3", "To Refund: 1.00"),
|
||||
TicketScreen.toRefundLineContains("Combo Product 6", "To Refund: 1.00"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ProductComboMaxFreeQtyTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Desk accessories combo
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.checkTotal("40.00"),
|
||||
combo.select("Combo Product 3"),
|
||||
combo.checkTotal("42.00"),
|
||||
|
||||
// Desks combo
|
||||
combo.select("Combo Product 5"),
|
||||
combo.checkProductQty("Combo Product 5", "1"),
|
||||
combo.select("Combo Product 5"),
|
||||
combo.select("Combo Product 5"),
|
||||
// Check that we cannot exceed the combo 'max_qty' which is 2
|
||||
combo.checkProductQty("Combo Product 5", "2"),
|
||||
combo.checkTotal("46.00"),
|
||||
combo.clickQtyBtnMinus("Combo Product 5"),
|
||||
combo.checkProductQty("Combo Product 5", "1"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.checkProductQty("Combo Product 4", "1"),
|
||||
combo.checkTotal("44.00"),
|
||||
combo.isConfirmationButtonDisabled(),
|
||||
|
||||
// Chairs combo
|
||||
combo.select("Combo Product 6"),
|
||||
combo.clickQtyBtnAdd("Combo Product 6"),
|
||||
combo.checkProductQty("Combo Product 6", "2"),
|
||||
// Confirmation should be enabled as we have selected the "min" qty for each combo
|
||||
Utils.negateStep(combo.isConfirmationButtonDisabled()),
|
||||
// As for chairs combo : 'qty_max' > 'qty_free', we can still select the product, but we'll pay them as extra (combo 'base_price')
|
||||
combo.checkTotal("44.00"),
|
||||
combo.select("Combo Product 7"),
|
||||
combo.clickQtyBtnAdd("Combo Product 7"),
|
||||
combo.clickQtyBtnAdd("Combo Product 7"),
|
||||
combo.checkProductQty("Combo Product 7", "3"),
|
||||
combo.checkTotal("134.00"),
|
||||
|
||||
Dialog.confirm(),
|
||||
inLeftSide([
|
||||
...ProductScreen.selectedOrderlineHasDirect("Office Combo", "1", "151.97"),
|
||||
]),
|
||||
ProductScreen.totalAmountIs("151.98"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ProductComboChangePricelist", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 2"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
inLeftSide([
|
||||
...ProductScreen.orderComboLineHas("Combo Product 2", "1.0"),
|
||||
...ProductScreen.orderComboLineHas("Combo Product 4", "1.0"),
|
||||
...ProductScreen.orderComboLineHas("Combo Product 6", "1.0"),
|
||||
]),
|
||||
ProductScreen.totalAmountIs("47.33"),
|
||||
ProductScreen.clickPriceList("sale 10%"),
|
||||
inLeftSide([
|
||||
...ProductScreen.orderComboLineHas("Combo Product 2", "1.0"),
|
||||
...ProductScreen.orderComboLineHas("Combo Product 4", "1.0"),
|
||||
...ProductScreen.orderComboLineHas("Combo Product 6", "1.0"),
|
||||
]),
|
||||
ProductScreen.totalAmountIs("42.60"),
|
||||
ProductScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ProductComboDiscountTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
ProductScreen.checkProductExtraPrice("Combo Product 3", "2"),
|
||||
combo.select("Combo Product 2"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
inLeftSide([Numpad.click("%"), Numpad.click("2"), Numpad.click("0")]),
|
||||
ProductScreen.totalAmountIs("80.00"),
|
||||
ProductScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_combo_item_image_display", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.checkImgAndSelect("Combo Product 2", true),
|
||||
combo.checkImgAndSelect("Combo Product 4", true),
|
||||
combo.checkImgAndSelect("Combo Product 6", true),
|
||||
Dialog.confirm(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_combo_item_image_not_display", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.checkImgAndSelect("Combo Product 2", false),
|
||||
combo.checkImgAndSelect("Combo Product 4", false),
|
||||
combo.checkImgAndSelect("Combo Product 6", false),
|
||||
Dialog.confirm(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_combo_no_free_item", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Desk accessories combo
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 1"),
|
||||
combo.select("Combo Product 2"),
|
||||
combo.select("Combo Product 3"),
|
||||
combo.checkTotal(`${10 * 3 + 2 + 40}.00`),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 5"),
|
||||
combo.checkTotal(`${72 + 20 * 2 + 2}.00`),
|
||||
combo.select("Combo Product 6"),
|
||||
combo.select("Combo Product 7"),
|
||||
combo.select("Combo Product 8"),
|
||||
combo.checkTotal(`${114 + 30 * 3 + 5}.00`),
|
||||
Dialog.confirm(),
|
||||
inLeftSide([
|
||||
...ProductScreen.selectedOrderlineHasDirect("Office Combo", "1", "232.10"),
|
||||
]),
|
||||
ProductScreen.totalAmountIs("232.10"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as combo from "@point_of_sale/../tests/pos/tours/utils/combo_popup_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { refresh } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as ProductConfigurator from "@point_of_sale/../tests/pos/tours/utils/product_configurator_util";
|
||||
|
||||
const setupProductConfigurator = [
|
||||
ProductConfigurator.pickColor("Blue"),
|
||||
ProductConfigurator.pickSelect("Wood"),
|
||||
ProductConfigurator.pickRadio("Other"),
|
||||
ProductConfigurator.fillCustomAttribute("Azerty"),
|
||||
ProductConfigurator.pickMulti("Cushion"),
|
||||
ProductConfigurator.pickMulti("Headrest"),
|
||||
].flat();
|
||||
|
||||
const checkProductConfigurator = [
|
||||
ProductConfigurator.selectedColor("Blue"),
|
||||
ProductConfigurator.selectedSelect("Wood"),
|
||||
ProductConfigurator.selectedRadio("Other"),
|
||||
ProductConfigurator.selectedCustomAttribute("Azerty"),
|
||||
ProductConfigurator.selectedMulti("Cushion"),
|
||||
ProductConfigurator.selectedMulti("Headrest"),
|
||||
].flat();
|
||||
|
||||
const checkConfiguredLine = (isCombo = false) => {
|
||||
const method = isCombo ? ProductScreen.orderComboLineHas : ProductScreen.orderLineHas;
|
||||
return [
|
||||
method(
|
||||
"Configurable Chair",
|
||||
"1.0",
|
||||
"",
|
||||
"Blue, Wood, Fabrics: Other: Azerty, Cushion, Headrest"
|
||||
),
|
||||
].flat();
|
||||
};
|
||||
|
||||
registry.category("web_tour.tours").add("test_line_configurators_product", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Configurable Chair"),
|
||||
...setupProductConfigurator,
|
||||
Dialog.confirm(),
|
||||
|
||||
inLeftSide([
|
||||
...ProductScreen.longPressOrderline("Configurable Chair"),
|
||||
Dialog.discard(),
|
||||
...checkConfiguredLine(false),
|
||||
...ProductScreen.longPressOrderline("Configurable Chair"),
|
||||
...checkProductConfigurator,
|
||||
Dialog.confirm(),
|
||||
...checkConfiguredLine(false),
|
||||
]),
|
||||
refresh(),
|
||||
inLeftSide([
|
||||
...checkConfiguredLine(false),
|
||||
...ProductScreen.longPressOrderline("Configurable Chair"),
|
||||
...checkProductConfigurator,
|
||||
Dialog.discard(),
|
||||
...checkConfiguredLine(false),
|
||||
]),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_line_configurators_combo", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
// Select first combo (combo 1)
|
||||
combo.select("Combo Product 2"),
|
||||
combo.isSelected("Combo Product 2"),
|
||||
|
||||
// Open Product Configurator + Configure + Confirm (combo 2)
|
||||
combo.select("Configurable Chair"),
|
||||
...setupProductConfigurator,
|
||||
Dialog.confirm(),
|
||||
|
||||
// Select it again
|
||||
combo.select("Configurable Chair"),
|
||||
...setupProductConfigurator,
|
||||
Dialog.confirm(),
|
||||
|
||||
// Select last combo (combo 3)
|
||||
combo.select("Combo Product 6"),
|
||||
combo.isSelected("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
|
||||
inLeftSide([
|
||||
...ProductScreen.orderComboLineHas("Combo Product 2", "1.0"),
|
||||
...checkConfiguredLine(true),
|
||||
...ProductScreen.orderComboLineHas("Combo Product 6", "1.0"),
|
||||
|
||||
// Edit combo
|
||||
...ProductScreen.longPressOrderline("Office Combo"),
|
||||
combo.isSelected("Combo Product 2"),
|
||||
combo.isSelected("Configurable Chair"),
|
||||
combo.isSelected("Combo Product 6"),
|
||||
Dialog.confirm("Add to Order"),
|
||||
|
||||
...ProductScreen.orderComboLineHas("Combo Product 2", "1.0"),
|
||||
...checkConfiguredLine(true),
|
||||
...ProductScreen.orderComboLineHas("Combo Product 6", "1.0"),
|
||||
]),
|
||||
refresh(),
|
||||
inLeftSide([
|
||||
...ProductScreen.longPressOrderline("Office Combo"),
|
||||
combo.isSelected("Combo Product 2"),
|
||||
combo.isSelected("Configurable Chair"),
|
||||
combo.isSelected("Combo Product 6"),
|
||||
|
||||
combo.select("Configurable Chair"),
|
||||
...checkProductConfigurator,
|
||||
Dialog.confirm(),
|
||||
Dialog.confirm("Add to Order"),
|
||||
|
||||
...ProductScreen.orderComboLineHas("Combo Product 2", "1.0"),
|
||||
...checkConfiguredLine(true),
|
||||
...ProductScreen.orderComboLineHas("Combo Product 6", "1.0"),
|
||||
|
||||
...ProductScreen.longPressOrderline("Office Combo"),
|
||||
Dialog.cancel(),
|
||||
...ProductScreen.longPressOrderline("Office Combo"),
|
||||
combo.isSelected("Configurable Chair"),
|
||||
Dialog.confirm("Add to Order"),
|
||||
]),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
function logText(displayText) {
|
||||
console.log(
|
||||
"\n\n┏" + "━".repeat(displayText.length) + "┓",
|
||||
`\n┃${displayText}┃`,
|
||||
"\n┗" + "━".repeat(displayText.length) + "┛\n"
|
||||
);
|
||||
}
|
||||
|
||||
registry.category("web_tour.tours").add("tourSessionOpenProductPerformance", {
|
||||
steps: () =>
|
||||
[
|
||||
{
|
||||
trigger: "body",
|
||||
timeout: 25000,
|
||||
async run({ waitFor }) {
|
||||
await waitFor("body:not(:has(.pos-loader))", { timeout: 20000 });
|
||||
const startTime = performance.timeOrigin;
|
||||
const endTime = Date.now();
|
||||
const loadingTimeSec = (endTime - startTime) / 1000;
|
||||
logText(
|
||||
` POS loading time: ${loadingTimeSec.toFixed(2)} seconds for 20000 products`
|
||||
);
|
||||
},
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("invoicePoSOrderWithSelfInvocing", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "input[name='pos_reference']",
|
||||
run: "edit 2500-002-00002",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='date_order']",
|
||||
run: function () {
|
||||
const date_order = luxon.DateTime.now();
|
||||
document.querySelector(".o_portal_wrap input[name='date_order']").value =
|
||||
date_order.toFormat("yyyy-MM-dd");
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='ticket_code']",
|
||||
run: "edit inPoS",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap button:contains('Request Invoice')",
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='name']",
|
||||
run: "edit Anant Parmar",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='phone']",
|
||||
run: "edit +911234567890",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='email']",
|
||||
run: "edit test@test.com",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='company_name']",
|
||||
run: function () {
|
||||
const companyNameInput = document.querySelector("input[name='company_name']");
|
||||
if (companyNameInput.hasAttribute("readonly")) {
|
||||
throw new Error("The company name input must not be readonly.");
|
||||
}
|
||||
companyNameInput.value = "TEST COMPANY NAME";
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='vat']",
|
||||
run: function () {
|
||||
const vatInput = document.querySelector("input[name='vat']");
|
||||
if (vatInput.hasAttribute("readonly")) {
|
||||
throw new Error("The vat input must not be readonly.");
|
||||
}
|
||||
vatInput.value = "1234567890";
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='street']",
|
||||
run: "edit 131, Satyamcity society",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='street2']",
|
||||
run: "edit opposite new rto office",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='city']",
|
||||
run: "edit palanpur",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap input[name='zip']",
|
||||
run: "edit 385001",
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap select[name='country_id']",
|
||||
run: function () {
|
||||
const countrySelect = document.querySelector("select[name='country_id']");
|
||||
if (Array.from(countrySelect.classList).includes("d-none")) {
|
||||
throw new Error("The language selector must not be hidden.");
|
||||
}
|
||||
countrySelect.value = "233";
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap select[name='state_id']",
|
||||
run: function () {
|
||||
const stateSelect = document.querySelector("select[name='state_id']");
|
||||
if (Array.from(stateSelect.classList).includes("d-none")) {
|
||||
throw new Error("The language selector must not be hidden.");
|
||||
}
|
||||
stateSelect.value = "19";
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_portal_wrap button:contains('Get my invoice')",
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: ".rounded.text-bg-success.fw-normal.badge",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/* global posmodel */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Pricelist from "@point_of_sale/../tests/pos/tours/utils/pricelist_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as OfflineUtil from "@point_of_sale/../tests/generic_helpers/offline_util";
|
||||
import * as ProductConfigurator from "@point_of_sale/../tests/pos/tours/utils/product_configurator_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 { refresh, scan_barcode } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("pos_pricelist", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Pricelist.setUp(),
|
||||
Pricelist.waitForUnitTest(),
|
||||
Dialog.confirm("Open Register"),
|
||||
OfflineUtil.setOfflineMode(),
|
||||
ProductScreen.clickPriceList("Fixed", true, "Public Pricelist"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Acme Corporation"),
|
||||
ProductScreen.clickPriceList("Public Pricelist", true),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Lumber Inc"),
|
||||
ProductScreen.clickPriceList("Public Pricelist", true),
|
||||
ProductScreen.clickDisplayedProduct("Wall Shelf", true, "1"),
|
||||
ProductScreen.clickPriceList("min_quantity ordering"),
|
||||
ProductScreen.clickReview(),
|
||||
{ ...ProductScreen.clickLine("Wall Shelf")[0], isActive: ["mobile"] },
|
||||
Numpad.click("2"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Wall Shelf", "2"),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
...Order.hasTotal(`$ 2.00`),
|
||||
ProductScreen.clickDisplayedProduct("Small Shelf", true, "1"),
|
||||
ProductScreen.clickReview(),
|
||||
{ ...ProductScreen.clickLine("Small Shelf")[0], isActive: ["mobile"] },
|
||||
Numpad.click("Price"),
|
||||
Numpad.isActive("Price"),
|
||||
Numpad.click("5"),
|
||||
...Order.hasLine({ productName: "Small Shelf", price: "5.0", withClass: ".selected" }),
|
||||
Numpad.click("Qty"),
|
||||
Numpad.isActive("Qty"),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.clickPriceList("Public Pricelist"),
|
||||
...Order.hasTotal(`$ 8.96`),
|
||||
ProductScreen.clickPriceList("min_quantity ordering"),
|
||||
OfflineUtil.setOnlineMode(),
|
||||
ProductScreen.closePos(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
// # With test_pricelist set on the order:
|
||||
// # - First banana variant will be priced at 100 via product variant
|
||||
// # - Second banana variant will be priced at 150 via product variant
|
||||
// # - Third banana variant will be priced at 20 via product template
|
||||
// # - First apple variant will be priced at 100 via product variant
|
||||
// # - All product without rules and with product_category will be priced at 500
|
||||
const test_pricelists_in_pos_steps = [
|
||||
ProductScreen.clickPriceList("Test Pricelist"),
|
||||
scan_barcode("banana_0"),
|
||||
ProductScreen.selectedOrderlineHas("Banana", "1", "100.0", "BIG"),
|
||||
scan_barcode("banana_1"),
|
||||
ProductScreen.selectedOrderlineHas("Banana", "1", "150.0", "MEDIUM"),
|
||||
scan_barcode("banana_2"),
|
||||
ProductScreen.selectedOrderlineHas("Banana", "1", "20.0", "SMALL"),
|
||||
scan_barcode("apple_0"),
|
||||
ProductScreen.selectedOrderlineHas("Apple", "1", "100.0", "BIG"),
|
||||
scan_barcode("apple_1"),
|
||||
ProductScreen.selectedOrderlineHas("Apple", "1", "500.0", "MEDIUM"),
|
||||
scan_barcode("apple_2"),
|
||||
ProductScreen.selectedOrderlineHas("Apple", "1", "500.0", "SMALL"),
|
||||
|
||||
ProductScreen.clickPriceList("Percentage Pricelist"),
|
||||
scan_barcode("banana_0"),
|
||||
ProductScreen.selectedOrderlineHas("Banana", "2", "100.0", "BIG"),
|
||||
scan_barcode("banana_1"),
|
||||
ProductScreen.selectedOrderlineHas("Banana", "2", "150.0", "MEDIUM"),
|
||||
scan_barcode("banana_2"),
|
||||
ProductScreen.selectedOrderlineHas("Banana", "2", "20.0", "SMALL"),
|
||||
scan_barcode("apple_0"),
|
||||
ProductScreen.selectedOrderlineHas("Apple", "2", "100.0", "BIG"),
|
||||
scan_barcode("apple_1"),
|
||||
ProductScreen.selectedOrderlineHas("Apple", "2", "500.0", "MEDIUM"),
|
||||
scan_barcode("apple_2"),
|
||||
ProductScreen.selectedOrderlineHas("Apple", "2", "500.0", "SMALL"),
|
||||
|
||||
// Try scan a product with nested pricelist on variant
|
||||
scan_barcode("pear_0"),
|
||||
ProductScreen.selectedOrderlineHas("Pear", "1", "10.0", "BIG"),
|
||||
scan_barcode("pear_1"),
|
||||
ProductScreen.selectedOrderlineHas("Pear", "1", "20.0", "MEDIUM"),
|
||||
scan_barcode("pear_2"),
|
||||
ProductScreen.selectedOrderlineHas("Pear", "1", "30.0", "SMALL"),
|
||||
|
||||
// Try scan a product with nested pricelist on template
|
||||
scan_barcode("lime_0"),
|
||||
ProductScreen.selectedOrderlineHas("Lime", "1", "50.0", "BIG"),
|
||||
scan_barcode("lime_1"),
|
||||
ProductScreen.selectedOrderlineHas("Lime", "1", "100.0", "MEDIUM"),
|
||||
scan_barcode("lime_2"),
|
||||
ProductScreen.selectedOrderlineHas("Lime", "1", "200.0", "SMALL"),
|
||||
|
||||
// Try scan a product with nested pricelist on category
|
||||
scan_barcode("orange_0"),
|
||||
ProductScreen.selectedOrderlineHas("Orange", "1", "500.0", "BIG"),
|
||||
scan_barcode("orange_1"),
|
||||
ProductScreen.selectedOrderlineHas("Orange", "1", "300.0", "MEDIUM"),
|
||||
scan_barcode("orange_2"),
|
||||
ProductScreen.selectedOrderlineHas("Orange", "1", "250.0", "SMALL"),
|
||||
|
||||
// Try scan a product with no pricelist rules
|
||||
scan_barcode("kiwi_0"),
|
||||
ProductScreen.selectedOrderlineHas("Kiwi", "1", "10.0", "BIG"),
|
||||
scan_barcode("kiwi_1"),
|
||||
ProductScreen.selectedOrderlineHas("Kiwi", "1", "5.0", "MEDIUM"),
|
||||
scan_barcode("kiwi_2"),
|
||||
ProductScreen.selectedOrderlineHas("Kiwi", "1", "5.0", "SMALL"),
|
||||
|
||||
// Test if post-loaded product with attribute open the configrator
|
||||
scan_barcode("cherry_3"),
|
||||
Chrome.waitRequest(),
|
||||
{
|
||||
content: "Click hided product with attribute",
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
const productTemplate = posmodel.models["product.template"].find(
|
||||
(p) => p.name === "Cherry"
|
||||
);
|
||||
|
||||
posmodel.addLineToCurrentOrder({
|
||||
product_tmpl_id: productTemplate,
|
||||
});
|
||||
},
|
||||
},
|
||||
ProductConfigurator.pickRadio("BIG"),
|
||||
ProductConfigurator.pickRadio("GREEN"),
|
||||
ProductConfigurator.isUnavailable("RED"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.00" }),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
];
|
||||
|
||||
registry.category("web_tour.tours").add("test_pricelists_in_pos", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
...test_pricelists_in_pos_steps,
|
||||
refresh(), // Check pricelist sorting after a refresh
|
||||
...test_pricelists_in_pos_steps,
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as ProductConfigurator from "@point_of_sale/../tests/pos/tours/utils/product_configurator_util";
|
||||
import * as combo from "@point_of_sale/../tests/pos/tours/utils/combo_popup_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { negateStep } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("ProductConfiguratorTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Click on Configurable Chair product
|
||||
ProductScreen.clickDisplayedProduct("Configurable Chair"),
|
||||
ProductConfigurator.selectedColor("Red"),
|
||||
ProductConfigurator.selectedSelect("Metal"),
|
||||
ProductConfigurator.selectedRadio("Leather"),
|
||||
|
||||
// Cancel configuration, not product should be in order
|
||||
Dialog.cancel(),
|
||||
ProductScreen.orderIsEmpty(),
|
||||
|
||||
// Click on Configurable Chair product
|
||||
ProductScreen.clickDisplayedProduct("Configurable Chair"),
|
||||
|
||||
// Select attributes
|
||||
ProductConfigurator.pickRadio("Other"),
|
||||
ProductConfigurator.fillCustomAttribute("Custom Fabric"),
|
||||
ProductConfigurator.pickMulti("Cushion"),
|
||||
ProductConfigurator.pickMulti("Headrest"),
|
||||
|
||||
ProductConfigurator.selectedColor("Red"),
|
||||
ProductConfigurator.selectedSelect("Metal"),
|
||||
ProductConfigurator.selectedRadio("Other"),
|
||||
ProductConfigurator.selectedCustomAttribute("Custom Fabric"),
|
||||
ProductConfigurator.selectedMulti("Cushion"),
|
||||
ProductConfigurator.selectedMulti("Headrest"),
|
||||
|
||||
// Check that the product has been added to the order with correct attributes and price
|
||||
Dialog.confirm(),
|
||||
ProductScreen.selectedOrderlineHas(
|
||||
"Configurable Chair",
|
||||
"1",
|
||||
"11.0",
|
||||
"Red, Metal, Fabrics: Other: Custom Fabric, Cushion, Headrest"
|
||||
),
|
||||
|
||||
// Orderlines with the same attributes should be merged
|
||||
ProductScreen.clickDisplayedProduct("Configurable Chair"),
|
||||
ProductConfigurator.pickRadio("Other"),
|
||||
ProductConfigurator.fillCustomAttribute("Custom Fabric"),
|
||||
ProductConfigurator.pickMulti("Cushion"),
|
||||
ProductConfigurator.pickMulti("Headrest"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.selectedOrderlineHas(
|
||||
"Configurable Chair",
|
||||
"2",
|
||||
"22.0",
|
||||
"Red, Metal, Fabrics: Other: Custom Fabric, Cushion, Headrest"
|
||||
),
|
||||
|
||||
// Orderlines with different attributes shouldn't be merged
|
||||
ProductScreen.clickDisplayedProduct("Configurable Chair"),
|
||||
ProductConfigurator.pickColor("Blue"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.selectedOrderlineHas(
|
||||
"Configurable Chair",
|
||||
"1",
|
||||
"10.0",
|
||||
"Blue, Metal, Leather"
|
||||
),
|
||||
|
||||
// Inactive variant attributes should not be displayed
|
||||
ProductScreen.clickDisplayedProduct("Configurable Chair"),
|
||||
// Active: Other and Leather, Inactive: Wool
|
||||
ProductConfigurator.numberRadioOptions(2),
|
||||
Dialog.cancel(),
|
||||
|
||||
// Reopen configuration and discard changes --> Come back to previous attributes
|
||||
ProductScreen.openCartMobile(),
|
||||
ProductScreen.longPressOrderline("Configurable Chair"),
|
||||
ProductConfigurator.selectedColor("Red"),
|
||||
ProductConfigurator.selectedSelect("Metal"),
|
||||
ProductConfigurator.selectedRadio("Other"),
|
||||
ProductConfigurator.selectedCustomAttribute("Custom Fabric"),
|
||||
ProductConfigurator.selectedMulti("Cushion"),
|
||||
ProductConfigurator.selectedMulti("Headrest"),
|
||||
|
||||
ProductConfigurator.pickColor("Blue"),
|
||||
ProductConfigurator.fillCustomAttribute("Azerty"),
|
||||
Dialog.cancel(),
|
||||
ProductScreen.clickLine("Configurable Chair", 2),
|
||||
ProductScreen.selectedOrderlineHasDirect(
|
||||
"Configurable Chair",
|
||||
"2",
|
||||
"22.0",
|
||||
"Red, Metal, Fabrics: Other: Custom Fabric, Cushion, Headrest"
|
||||
),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosProductWithDynamicAttributes", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.searchProduct("Non Existing Product"),
|
||||
ProductScreen.productIsDisplayed("Dynamic Product").map(negateStep),
|
||||
ProductScreen.searchProduct("Dynamic Product"),
|
||||
ProductScreen.productIsDisplayed("Dynamic Product"),
|
||||
ProductScreen.clickDisplayedProduct("Dynamic Product"),
|
||||
ProductConfigurator.pickRadio("Test 1"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.selectedOrderlineHas("Dynamic Product", "1", "1.15", "Test 1"),
|
||||
ProductScreen.clickDisplayedProduct("Dynamic Product"),
|
||||
ProductConfigurator.pickRadio("Test 2"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.selectedOrderlineHas("Dynamic Product", "1", "12.65", "Test 2"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_attribute_order", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Product Test"),
|
||||
ProductConfigurator.pickRadio("Value 1"),
|
||||
ProductConfigurator.pickRadio("Value 2"),
|
||||
ProductConfigurator.pickRadio("Value 3"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.selectedOrderlineHas(
|
||||
"Product Test",
|
||||
"1",
|
||||
"10",
|
||||
"Value 1, Value 2, Value 3"
|
||||
),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_combo_variant_mix", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Click on Configurable Chair product
|
||||
ProductScreen.clickDisplayedProduct("Test Product Combo"),
|
||||
combo.select("Test Product (Large)"),
|
||||
Dialog.is("Attribute selection"),
|
||||
ProductConfigurator.pickRadio("Blue"),
|
||||
Dialog.confirm("Add"),
|
||||
Dialog.confirm(),
|
||||
inLeftSide(
|
||||
[
|
||||
Order.hasLine({
|
||||
product: "Test Product",
|
||||
quantity: 1,
|
||||
price: 20.0,
|
||||
attributes: "Blue, Large",
|
||||
}),
|
||||
].flat()
|
||||
),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_cross_exclusion_attribute_values", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product 1"),
|
||||
ProductConfigurator.pickRadio("attribute_1_value_1"),
|
||||
[
|
||||
{
|
||||
content: `check radio attribute with name attribute_2_value_1 is muted`,
|
||||
trigger: `.modal .attribute-name-cell:contains('attribute_2_value_1') span.text-muted`,
|
||||
},
|
||||
],
|
||||
ProductConfigurator.pickRadio("attribute_2_value_1"),
|
||||
ProductConfigurator.isAddDisabled(),
|
||||
ProductConfigurator.pickRadio("attribute_2_value_2"),
|
||||
[
|
||||
{
|
||||
content: `check radio attribute with name attribute_1_value_2 is muted`,
|
||||
trigger: `.modal .attribute-name-cell:contains('attribute_1_value_2') span.text-muted`,
|
||||
},
|
||||
],
|
||||
ProductConfigurator.pickRadio("attribute_1_value_2"),
|
||||
ProductConfigurator.isAddDisabled(),
|
||||
ProductConfigurator.pickRadio("attribute_1_value_1"),
|
||||
ProductConfigurator.pickRadio("attribute_2_value_2"),
|
||||
ProductConfigurator.isAddEnabled(),
|
||||
ProductConfigurator.pickRadio("attribute_1_value_2"),
|
||||
ProductConfigurator.pickRadio("attribute_2_value_1"),
|
||||
ProductConfigurator.isAddEnabled(),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_exclusion_attribute_values", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Configurable Chair"),
|
||||
ProductConfigurator.pickColor("Red"),
|
||||
ProductConfigurator.pickSelect("Metal"),
|
||||
ProductConfigurator.isUnavailable("Other"),
|
||||
ProductConfigurator.isUnavailable("Wool"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_custom_attribute_alone_displayed", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Only Custom"),
|
||||
ProductConfigurator.fillCustomAttribute("Filling"),
|
||||
ProductConfigurator.selectedCustomAttribute("Filling"),
|
||||
Dialog.confirm(),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_product_configurator_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Configurable Product"),
|
||||
ProductConfigurator.priceIs("13.20"), // 10 (Small) + 2 (Red) + 1.2 (10% tax)
|
||||
ProductConfigurator.pickRadio("Large"),
|
||||
ProductConfigurator.priceIs("14.30"), // 10 + 1 (Large) + 2 (Red) + 1.3 (10% tax)
|
||||
ProductConfigurator.pickRadio("Blue"),
|
||||
ProductConfigurator.priceIs("15.40"), // 10 + 1 (Large) + 3 (Blue) + 1.4 (10% tax)
|
||||
Dialog.confirm(),
|
||||
ProductScreen.totalAmountIs("15.40"),
|
||||
ProductScreen.clickPriceList("Pricelist 2"),
|
||||
ProductScreen.totalAmountIs("22.00"),
|
||||
ProductScreen.clickDisplayedProduct("Configurable Product"),
|
||||
ProductConfigurator.priceIs("22.00"), // 20 (pricelist 2) + 2 (10% tax)
|
||||
ProductConfigurator.pickRadio("Blue"),
|
||||
ProductConfigurator.priceIs("22.00"), // 20 (pricelist 2) + 2 (10% tax)
|
||||
Dialog.confirm(),
|
||||
ProductScreen.totalAmountIs("44.00"),
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.clickFiscalPosition("Include to Exclude"),
|
||||
ProductScreen.clickDisplayedProduct("Configurable Product"),
|
||||
ProductConfigurator.priceIs("12.00"), // 10 (Small) + 2 (Red)
|
||||
ProductConfigurator.pickRadio("Large"),
|
||||
ProductConfigurator.priceIs("13.00"), // 10 + 1 (Large) + 2 (Red)
|
||||
ProductConfigurator.pickRadio("Blue"),
|
||||
ProductConfigurator.priceIs("14.00"), // 10 + 1 (Large) + 3 (Blue)
|
||||
Dialog.confirm(),
|
||||
ProductScreen.totalAmountIs("14.00"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,128 @@
|
|||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as ProductConfiguratorPopup from "@point_of_sale/../tests/pos/tours/utils/product_configurator_util";
|
||||
|
||||
function check_variant_price(product, choices, price) {
|
||||
const steps = [...ProductScreen.clickDisplayedProduct(product)];
|
||||
for (const choice of choices) {
|
||||
steps.push(...ProductConfiguratorPopup.pickRadio(choice));
|
||||
}
|
||||
steps.push(
|
||||
Dialog.confirm(),
|
||||
...ProductScreen.totalAmountIs(price),
|
||||
...ProductScreen.clickNumpad("⌫"),
|
||||
...ProductScreen.clickNumpad("⌫")
|
||||
);
|
||||
return steps.flat();
|
||||
}
|
||||
|
||||
registry.category("web_tour.tours").add("test_integration_dynamic_variant_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
check_variant_price("A dynamic product", ["dyn1"], "1.00"),
|
||||
check_variant_price("A dynamic product", ["dyn2"], "6.00"),
|
||||
check_variant_price("A dynamic product", ["dyn3"], "11.00"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_integration_always_variant_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
check_variant_price("A always product", ["S"], "1.00"),
|
||||
check_variant_price("A always product", ["M"], "6.00"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_integration_never_variant_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
check_variant_price("A never product", ["extra"], "1.00"),
|
||||
check_variant_price("A never product", ["second"], "6.00"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_integration_dynamic_always_variant_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
check_variant_price("A dyn/alw product", ["dyn1", "S"], "1.00"),
|
||||
check_variant_price("A dyn/alw product", ["dyn1", "M"], "6.00"),
|
||||
check_variant_price("A dyn/alw product", ["dyn2", "S"], "11.00"),
|
||||
check_variant_price("A dyn/alw product", ["dyn2", "M"], "16.00"),
|
||||
check_variant_price("A dyn/alw product", ["dyn3", "S"], "21.00"),
|
||||
check_variant_price("A dyn/alw product", ["dyn3", "M"], "26.00"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_integration_dynamic_never_variant_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
check_variant_price("A dyn/nev product", ["dyn1", "extra"], "1.00"),
|
||||
check_variant_price("A dyn/nev product", ["dyn1", "second"], "6.00"),
|
||||
check_variant_price("A dyn/nev product", ["dyn2", "extra"], "11.00"),
|
||||
check_variant_price("A dyn/nev product", ["dyn2", "second"], "16.00"),
|
||||
check_variant_price("A dyn/nev product", ["dyn3", "extra"], "21.00"),
|
||||
check_variant_price("A dyn/nev product", ["dyn3", "second"], "26.00"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_integration_always_never_variant_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
check_variant_price("A alw/nev product", ["S", "extra"], "1.00"),
|
||||
check_variant_price("A alw/nev product", ["S", "second"], "6.00"),
|
||||
check_variant_price("A alw/nev product", ["M", "extra"], "11.00"),
|
||||
check_variant_price("A alw/nev product", ["M", "second"], "16.00"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_integration_dynamic_always_never_variant_price", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn1", "S", "extra"], "1.00"),
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn1", "S", "second"], "1.50"),
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn1", "M", "extra"], "6.00"),
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn1", "M", "second"], "6.50"),
|
||||
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn2", "S", "extra"], "11.00"),
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn2", "S", "second"], "11.50"),
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn2", "M", "extra"], "16.00"),
|
||||
check_variant_price("A dyn/alw/nev product", ["dyn2", "M", "second"], "16.50"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_image_variants_displayed", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Image Product"),
|
||||
ProductConfiguratorPopup.checkImageVariantVisible(),
|
||||
ProductConfiguratorPopup.checkImageVariantTextVisible("First Image"),
|
||||
ProductConfiguratorPopup.checkImageVariantTextVisible("Second Image"),
|
||||
ProductConfiguratorPopup.checkImagePriceExtraVisible("$ 20"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
/* global posmodel */
|
||||
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as NumberPopup from "@point_of_sale/../tests/generic_helpers/number_popup_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import * as OfflineUtil from "@point_of_sale/../tests/generic_helpers/offline_util";
|
||||
import { run, negateStep } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("ReceiptScreenTour", {
|
||||
steps: () =>
|
||||
[
|
||||
// press close button in receipt screen
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
OfflineUtil.setOfflineMode(),
|
||||
ProductScreen.addOrderline("Letter Tray", "10", "5"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Full"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickShipLaterButton(),
|
||||
PaymentScreen.shippingLaterHighlighted(),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
ReceiptScreen.cashierNameExists("A"), // A simple PoS man! (Take the first word)
|
||||
Dialog.confirm("Continue with limited functionality"),
|
||||
//receipt had expected delivery printed
|
||||
ReceiptScreen.shippingDateExists(),
|
||||
ReceiptScreen.shippingDateIsToday(),
|
||||
// letter tray has 10% tax (search SRC)
|
||||
ReceiptScreen.totalAmountContains("55.0"),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// send email in receipt screen
|
||||
ProductScreen.addOrderline("Desk Pad", "6", "5", "30.0"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "6", "6", "36.0"),
|
||||
ProductScreen.addOrderline("Monitor Stand", "6", "1", "6.0"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.enterPaymentLineAmount("Cash", "70", true, { remaining: "2.0" }),
|
||||
PaymentScreen.clickNumpad("0"),
|
||||
PaymentScreen.fillPaymentLineAmountMobile("Cash", "700"),
|
||||
PaymentScreen.changeIs("628.0"),
|
||||
OfflineUtil.setOnlineMode(),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
ReceiptScreen.totalAmountContains("72.0"),
|
||||
ReceiptScreen.setEmail("test@receiptscreen.com"),
|
||||
ReceiptScreen.clickSend(),
|
||||
ReceiptScreen.emailIsSuccessful(),
|
||||
OfflineUtil.setOfflineMode(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// order with tip
|
||||
// check if tip amount is displayed
|
||||
ProductScreen.addOrderline("Desk Pad", "6", "5"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickTipButton(),
|
||||
{
|
||||
content: "click numpad button: 1",
|
||||
trigger: ".modal div.numpad button:contains(/^1/)",
|
||||
run: "click",
|
||||
},
|
||||
NumberPopup.isShown("1"),
|
||||
Dialog.confirm(),
|
||||
PaymentScreen.emptyPaymentlines("31.0"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
ReceiptScreen.totalAmountContains(`$ 30.00 + $ 1.00 tip`),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// Test customer note in receipt
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "5"),
|
||||
inLeftSide([
|
||||
{ ...ProductScreen.clickLine("Desk Pad")[0], isActive: ["mobile"] },
|
||||
...ProductScreen.addCustomerNote("Test customer note"),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
Order.hasLine({ customerNote: "Test customer note" }),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// Test that Internal notes are not available on receipt
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "5"),
|
||||
inLeftSide([
|
||||
{ ...ProductScreen.clickLine("Desk Pad")[0], isActive: ["mobile"] },
|
||||
...ProductScreen.addInternalNote("Test internal note"),
|
||||
...ProductScreen.clickSelectedLine("Desk Pad"),
|
||||
...ProductScreen.addInternalNote("Test internal note on order"),
|
||||
...Order.hasInternalNote("Test internal note on order"),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
negateStep(...Order.hasLine({ internalNote: "Test internal note" })),
|
||||
negateStep(...Order.hasInternalNote("Test internal note on order")),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
|
||||
// Test discount and original price
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "20"),
|
||||
inLeftSide([
|
||||
{ ...ProductScreen.clickLine("Desk Pad")[0], isActive: ["mobile"] },
|
||||
Numpad.click("%"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Pad", "1", "20"),
|
||||
Numpad.click("5"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Pad", "1", "19.0"),
|
||||
Numpad.click("."),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
Order.hasLine({ productName: "Desk Pad", priceNoDiscount: "20" }),
|
||||
ReceiptScreen.totalAmountContains("19.00"),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
OfflineUtil.setOnlineMode(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ReceiptScreenDiscountWithPricelistTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Test Product", "1"),
|
||||
ProductScreen.clickPriceList("special_pricelist"),
|
||||
inLeftSide(Order.hasLine({ productName: "Test Product", price: "6.30" })),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
Order.hasLine({ price: "6.30" }),
|
||||
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.addOrderline("Test Product", "1"),
|
||||
inLeftSide([
|
||||
{ ...ProductScreen.clickLine("Test Product")[0], isActive: ["mobile"] },
|
||||
Numpad.click("Price"),
|
||||
Numpad.isActive("Price"),
|
||||
Numpad.click("9"),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.noDiscountAmount(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("OrderPaidInCash", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Desk Pad", "5", "5"),
|
||||
inLeftSide(ProductScreen.orderLineHas("Desk Pad", "5")),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.validateButtonIsHighlighted(true),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.isShown(),
|
||||
// Close the session
|
||||
Chrome.clickMenuOption("Close Register"),
|
||||
ProductScreen.closeWithCashAmount("25"),
|
||||
ProductScreen.cashDifferenceIs("0.00"),
|
||||
{
|
||||
trigger: ".modal .modal-footer .btn:contains(close register)",
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: "button:contains(backend)",
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: "body",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ReceiptTrackingMethodTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.enterLotNumber("123456789", "lot"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.trackingMethodIsLot("123456789"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("point_of_sale.test_printed_receipt_tour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "5"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
|
||||
run(async () => {
|
||||
window.print = (e) => {
|
||||
const rendered = e.innerHTML;
|
||||
if (!rendered.includes("Desk Pad")) {
|
||||
throw new Error("Desk Pad is not present on the ticket");
|
||||
}
|
||||
|
||||
if (rendered.includes("5.00 / Units")) {
|
||||
throw new Error("The price should not be included on a basic receipt");
|
||||
}
|
||||
};
|
||||
await posmodel.printReceipt({ basic: true });
|
||||
}, "Basic receipt doesn't have price"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_auto_validate_force_done", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
{
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
posmodel.getOrder().payment_ids[0].setPaymentStatus("force_done");
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".send_force_done",
|
||||
run: "click",
|
||||
},
|
||||
ReceiptScreen.receiptIsThere(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_pos_order_shipping_date", {
|
||||
steps: () =>
|
||||
[
|
||||
ProductScreen.setTimeZone("America/New_York"),
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test with Address"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
{
|
||||
content: "click ship later button",
|
||||
trigger: ".button:contains('Ship Later')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "pick a date",
|
||||
trigger: '.modal-body input[type="date"]',
|
||||
run: () => {
|
||||
const input = document.querySelector('.modal-body input[type="date"]');
|
||||
const nextYear = new Date().getFullYear() + 1;
|
||||
input.value = `${nextYear}-05-30`;
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "click confirm button",
|
||||
trigger: ".btn:contains('Confirm')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Assert shipping date was set",
|
||||
trigger: ".payment-buttons .d-flex .btn span",
|
||||
run: () => {
|
||||
const spans = [
|
||||
...document.querySelectorAll(".payment-buttons .d-flex .btn span"),
|
||||
];
|
||||
const nextYear = new Date().getFullYear() + 1;
|
||||
const expectedDate = `05/30/${nextYear}`;
|
||||
if (!spans.some((span) => span.innerText === expectedDate)) {
|
||||
throw new Error("Expected shipping date is not set");
|
||||
}
|
||||
},
|
||||
},
|
||||
PaymentScreen.clickValidate(),
|
||||
{
|
||||
content: "Assert shipping date in receipt",
|
||||
trigger: ".pos-receipt-order-data",
|
||||
run: () => {
|
||||
const dateDiv = document.querySelector(".pos-receipt-order-data div");
|
||||
const nextYear = new Date().getFullYear() + 1;
|
||||
const expectedDate = `5/30/${nextYear}`;
|
||||
if (dateDiv.innerText !== expectedDate) {
|
||||
throw new Error("Expected shipping date is not set in receipt");
|
||||
}
|
||||
},
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* global posmodel */
|
||||
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_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 ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_sync_from_ui_one_by_one", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
{
|
||||
trigger: "body",
|
||||
content: "Create fake orders",
|
||||
run: async () => {
|
||||
// Create 5 orders that will be synced one by one
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const product = posmodel.models["product.template"].find(
|
||||
(p) => p.name === "Desk Pad"
|
||||
);
|
||||
const order = posmodel.createNewOrder();
|
||||
await posmodel.addLineToOrder({ product_tmpl_id: product }, order);
|
||||
posmodel.addPendingOrder([order.id]);
|
||||
}
|
||||
},
|
||||
},
|
||||
// Create one more order to be able to trigger the sync from the UI
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,651 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as PartnerList from "@point_of_sale/../tests/pos/tours/utils/partner_list_util";
|
||||
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as OfflineUtil from "@point_of_sale/../tests/generic_helpers/offline_util";
|
||||
import * as ProductConfiguratorPopup from "@point_of_sale/../tests/pos/tours/utils/product_configurator_util";
|
||||
|
||||
registry.category("web_tour.tours").add("TicketScreenTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
OfflineUtil.setOfflineMode(),
|
||||
Chrome.clickOrders(),
|
||||
Dialog.confirm("Continue with limited functionality"),
|
||||
OfflineUtil.setOnlineMode(),
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "3"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.deleteOrder("002"),
|
||||
Dialog.confirm(),
|
||||
TicketScreen.nthRowContains(1, "001"),
|
||||
TicketScreen.nthRowIsHighlighted(1),
|
||||
Chrome.clickRegister(),
|
||||
ProductScreen.orderIsEmpty(),
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "2"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.deleteOrder("001"),
|
||||
Dialog.confirm(),
|
||||
TicketScreen.nthRowContains(1, "001"),
|
||||
TicketScreen.nthRowIsHighlighted(1),
|
||||
Chrome.clickRegister(),
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "2"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.nthRowContains(1, "Partner Test 1", false),
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.addOrderline("Desk Pad", "1", "3"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 2"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.isShown(),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.nthRowContains(1, "Partner Test 2", false),
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.addOrderline("Desk Pad", "2", "4"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.nthRowContains(3, "Receipt"),
|
||||
TicketScreen.selectFilter("Receipt"),
|
||||
TicketScreen.nthRowContains(1, "Receipt"),
|
||||
TicketScreen.selectFilter("Payment"),
|
||||
TicketScreen.nthRowContains(1, "Payment"),
|
||||
TicketScreen.selectFilter("Ongoing"),
|
||||
TicketScreen.nthRowContains(1, "Ongoing"),
|
||||
TicketScreen.selectFilter("Active"),
|
||||
TicketScreen.nthRowContains(3, "Receipt"),
|
||||
TicketScreen.search("Receipt Number", "-00003"),
|
||||
TicketScreen.nthRowContains(1, "Receipt"),
|
||||
TicketScreen.search("Customer", "Partner Test 1"),
|
||||
TicketScreen.nthRowContains(1, "Partner Test 1", false),
|
||||
TicketScreen.search("Customer", "Partner Test 2"),
|
||||
TicketScreen.nthRowContains(1, "Partner Test 2", false),
|
||||
// Close the TicketScreen to see the current order which is in ReceiptScreen.
|
||||
// This is just to remove the search string in the search bar.
|
||||
Chrome.clickRegister(),
|
||||
ReceiptScreen.isShown(),
|
||||
// Open again the TicketScreen to check the Paid filter.
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.nthRowContains(1, "003"),
|
||||
TicketScreen.selectOrder("003"),
|
||||
// Pay the order that was in PaymentScreen.
|
||||
TicketScreen.selectFilter("Payment"),
|
||||
TicketScreen.selectOrder("002"),
|
||||
TicketScreen.loadSelectedOrder(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.isShown(),
|
||||
// Check that the Paid filter will show the 2 synced orders.
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.nthRowContains(1, "Partner Test 2", false),
|
||||
TicketScreen.nthRowContains(2, "003"),
|
||||
// Invoice order
|
||||
TicketScreen.selectOrder("003"),
|
||||
inLeftSide(Order.hasLine()),
|
||||
TicketScreen.clickControlButton("Invoice"),
|
||||
Dialog.confirm(),
|
||||
PartnerList.clickPartner("Partner Test 3"),
|
||||
TicketScreen.invoicePrinted(),
|
||||
TicketScreen.back(),
|
||||
// When going back, the ticket screen should be in its previous state.
|
||||
TicketScreen.filterIs("Paid"),
|
||||
// Test refund //
|
||||
Chrome.clickRegister(),
|
||||
ProductScreen.isShown(),
|
||||
ProductScreen.orderIsEmpty(),
|
||||
...ProductScreen.clickRefund(),
|
||||
//Filter should be automatically 'Paid'.
|
||||
TicketScreen.filterIs("Paid"),
|
||||
TicketScreen.selectOrder("003"),
|
||||
inLeftSide([
|
||||
...Order.hasLine({ productName: "Desk Pad", withClass: ".selected" }),
|
||||
Numpad.click("3"),
|
||||
Dialog.confirm(),
|
||||
]),
|
||||
Chrome.clickRegister(),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.isShown(),
|
||||
ProductScreen.orderIsEmpty(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("003"),
|
||||
inLeftSide(Order.hasLine({ productName: "Desk Pad", withClass: ".selected" })),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.toRefundTextContains("To Refund: 1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.isShown(),
|
||||
inLeftSide([
|
||||
...ProductScreen.clickLine("Desk Pad"),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Desk Pad", "-1"),
|
||||
// Try changing the refund line's qty, price, discount but altering of refund line not allowed.
|
||||
// Error popup should show.
|
||||
Numpad.click("2"),
|
||||
Dialog.confirm(),
|
||||
...["Price", "2"].map(Numpad.click),
|
||||
Dialog.confirm(),
|
||||
...["%", "5"].map(Numpad.click),
|
||||
Dialog.confirm(),
|
||||
]),
|
||||
// Check if the amount being refunded changed to 2.
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("003"),
|
||||
TicketScreen.toRefundTextContains("Refunding 1.00"),
|
||||
Chrome.clickRegister(),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
// Pay the refund order.
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
// Check refunded quantity.
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("003"),
|
||||
TicketScreen.refundedNoteContains("1.00 Refunded"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("FiscalPositionNoTaxRefund", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Product Test"),
|
||||
ProductScreen.totalAmountIs("100.00"),
|
||||
ProductScreen.clickFiscalPosition("No Tax"),
|
||||
ProductScreen.totalAmountIs("100.00"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.00" }),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
{ ...ProductScreen.back(), isActive: ["mobile"] },
|
||||
ProductScreen.totalAmountIs("100.00"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("LotRefundTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.clickOrders(),
|
||||
Chrome.clickOnScanButton(),
|
||||
TicketScreen.checkCameraIsOpen(),
|
||||
Chrome.clickOnScanButton(),
|
||||
Chrome.clickRegister(),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.enterLotNumber("123456789"),
|
||||
ProductScreen.selectedOrderlineHas("Product A", "1"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
Chrome.clickOnScanButton(),
|
||||
TicketScreen.checkCameraIsOpen(),
|
||||
Chrome.clickOnScanButton(),
|
||||
Chrome.clickRegister(),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.toRefundTextContains("To Refund: 1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
ProductScreen.clickLotIcon(),
|
||||
ProductScreen.checkFirstLotNumber("123456789"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("RefundFewQuantities", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Sugar"),
|
||||
inLeftSide([
|
||||
...["0", "."].map(Numpad.click),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Sugar", "0", "0.00"),
|
||||
...["0", "2"].map(Numpad.click),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Sugar", "0.02", "0.06"),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("0", "."),
|
||||
ProductScreen.clickNumpad("0", "2"),
|
||||
TicketScreen.toRefundTextContains("To Refund: 0.02"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
Order.hasLine("Sugar", "-0.02", "-0.06"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_order_refund_flow", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Desk Pad", "2", "3"),
|
||||
ProductScreen.addOrderline("Letter Tray", "3", "2"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
// First refund order
|
||||
ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.toRefundTextContains("To Refund: 1.00"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
Order.hasLine("Desk Pad", "-1"),
|
||||
// Second refund order
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
TicketScreen.toRefundTextContains("Refunding"),
|
||||
inLeftSide([...ProductScreen.clickLine("Letter Tray", "3.0")]),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.toRefundTextContains("To Refund: 1.00"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
// Verify refund order has only one line
|
||||
Order.hasLine("Letter Tray", "-1"),
|
||||
// Delete both refunding orders
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.deleteOrder("002"),
|
||||
Dialog.confirm(),
|
||||
TicketScreen.deleteOrder("003"),
|
||||
Dialog.confirm(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.selectOrder("001"),
|
||||
TicketScreen.noLinesToRefund(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_pay_unpaid_order_from_kiosk", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectOrder(2.53),
|
||||
TicketScreen.loadSelectedOrder(),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("refund_multiple_products_amounts_compliance", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product"),
|
||||
inLeftSide([
|
||||
...["2"].map(Numpad.click),
|
||||
...ProductScreen.selectedOrderlineHasDirect("Test Product", "2", "20"),
|
||||
]),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("2"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("LotTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.enterLotNumber("1"),
|
||||
ProductScreen.selectedOrderlineHas("Product A", "1"),
|
||||
inLeftSide(
|
||||
[
|
||||
ProductScreen.clickLotIcon(),
|
||||
ProductScreen.deleteNthLotNumber(1),
|
||||
ProductScreen.enterLotNumber("2", "serial", true),
|
||||
Order.hasLine({
|
||||
productName: "Product A",
|
||||
quantity: 1,
|
||||
}),
|
||||
ProductScreen.clickLotIcon(),
|
||||
ProductScreen.enterLotNumber("1"),
|
||||
Order.hasLine({
|
||||
productName: "Product A",
|
||||
quantity: 2.0,
|
||||
}),
|
||||
].flat()
|
||||
),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.enterLotNumber("3"),
|
||||
ProductScreen.selectedOrderlineHas("Product A", "3"),
|
||||
inLeftSide({
|
||||
trigger: ".info-list:contains('SN 3')",
|
||||
}),
|
||||
|
||||
// Verify if the serial number can be reused for the current order
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.enterLotNumber("5"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.enterLotNumber("3"),
|
||||
inLeftSide({
|
||||
trigger: ".info-list:not(:contains('SN 3'))",
|
||||
}),
|
||||
// Check auto assign lot number if there is only one available option
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
inLeftSide({
|
||||
trigger: ".info-list:contains('Lot Number 1001')",
|
||||
}),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("002"),
|
||||
inLeftSide(
|
||||
[Numpad.click("1"), ProductScreen.clickLine("Product B"), Numpad.click("1")].flat()
|
||||
),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("OrderTimeTour", {
|
||||
steps: () => {
|
||||
const validateDateStep = {
|
||||
content: "Validate order date is Today",
|
||||
trigger: ".orders .order-row:first .fw-bolder",
|
||||
run: function ({ anchor: displayedDateElement }) {
|
||||
if (displayedDateElement.textContent.trim() !== "Today") {
|
||||
throw new Error("Order date does not match local timezone");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const validateTimeStep = {
|
||||
content: "Validate order time matches local timezone",
|
||||
trigger: ".orders .order-row:first .small.text-muted",
|
||||
run: function ({ anchor: displayedTimeElement }) {
|
||||
const orderDateUTC = window.posmodel.getOrder().date_order;
|
||||
const orderDateTime = luxon.DateTime.fromSQL(orderDateUTC, {
|
||||
zone: "UTC",
|
||||
}).toLocal();
|
||||
if (orderDateTime.toFormat("HH:mm") !== displayedTimeElement.textContent.trim()) {
|
||||
throw new Error("Order time does not match local timezone");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad"),
|
||||
ProductScreen.setTimeZone("Pacific/Honolulu"),
|
||||
Chrome.clickOrders(),
|
||||
validateDateStep,
|
||||
validateTimeStep,
|
||||
ProductScreen.setTimeZone("Europe/Brussels"),
|
||||
Chrome.clickRegister(),
|
||||
Chrome.clickOrders(),
|
||||
validateDateStep,
|
||||
validateTimeStep,
|
||||
].flat();
|
||||
},
|
||||
});
|
||||
|
||||
registry
|
||||
.category("web_tour.tours")
|
||||
.add("test_consistent_refund_process_between_frontend_and_backend", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Desk Pad", "2", "4"),
|
||||
ProductScreen.clickPriceList("Percentage Pricelist"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
inLeftSide(Order.hasLine({ productName: "Desk Pad", withClass: ".selected" })),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.toRefundTextContains("To Refund: 1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_paid_order_with_archived_product_loads", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.nthRowContains(1, "0002"),
|
||||
TicketScreen.selectOrder("0002"),
|
||||
inLeftSide([
|
||||
...Order.hasLine({ productName: "Archived Product", withClass: ".selected" }),
|
||||
]),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_order_invoice_search", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
TicketScreen.search("Invoice Number", "00001"),
|
||||
TicketScreen.nthRowContains(1, "001", false),
|
||||
Chrome.clickMenuOption("Close Register"),
|
||||
{
|
||||
content: `Select button close register`,
|
||||
trigger: `button:contains(close register)`,
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
{
|
||||
content:
|
||||
"Verify that the order is paid; this ensures that the RPC process is complete.",
|
||||
trigger: ".orders .order-row:eq(0):has(.badge.rounded:contains(Paid))",
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_order_with_existing_serial", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Serial Product"),
|
||||
ProductScreen.enterExistingLotNumber("SN1"),
|
||||
ProductScreen.selectedOrderlineHas("Serial Product", "1.00"),
|
||||
inLeftSide({
|
||||
trigger: ".info-list:contains('SN SN1')",
|
||||
}),
|
||||
ProductScreen.clickDisplayedProduct("Serial Product"),
|
||||
ProductScreen.enterExistingLotNumber("SN2"),
|
||||
ProductScreen.selectedOrderlineHas("Serial Product", "2.00"),
|
||||
inLeftSide({
|
||||
trigger: ".info-list:contains('SN SN2')",
|
||||
}),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_lot_refund_lower_qty", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Serial Product"),
|
||||
ProductScreen.enterExistingLotNumbers(["SN1", "SN2"]),
|
||||
ProductScreen.selectedOrderlineHas("Serial Product", "2.00"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.toRefundTextContains("To Refund: 1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
{
|
||||
trigger: ".info-list:contains('SN SN1')",
|
||||
},
|
||||
ProductScreen.clickLotIcon(),
|
||||
{
|
||||
trigger: ".o-autocomplete--dropdown-item:contains('SN2')",
|
||||
},
|
||||
Dialog.confirm(),
|
||||
{
|
||||
content: "go back to the products",
|
||||
trigger: ".actionpad .back-button",
|
||||
run: "click",
|
||||
isActive: ["mobile"],
|
||||
},
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
{
|
||||
trigger: ".info-list:contains('SN SN2')",
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_refund_line_keep_attributes", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Donut"),
|
||||
ProductConfiguratorPopup.pickRadio("Sugar"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
ReceiptScreen.clickNextOrder(),
|
||||
ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.clickBack(),
|
||||
Order.hasLine({
|
||||
productName: "Donut",
|
||||
attributeLine: "Sugar",
|
||||
}),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_not_available_pricelist_not_set_on_order", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
Chrome.clickOrders(),
|
||||
TicketScreen.selectFilter("Paid"),
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.addOrderline("Desk Pad", "2", "3"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AA Customer"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
export function editShopConfiguration(shop) {
|
||||
return [
|
||||
{
|
||||
trigger: "body",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: ".o_main_navbar span:contains('Configuration')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains('Point of Sales')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `.o_data_cell[data-tooltip=${shop}]`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function openShopSession(shop) {
|
||||
return [
|
||||
{
|
||||
trigger: ".o_main_navbar .o-dropdown-item:contains('Dashboard')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `.o_kanban_record:contains(${shop}) .btn-primary`,
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function saveConfiguration() {
|
||||
return [
|
||||
{
|
||||
trigger: ".o_form_button_save",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function openProductForm(name) {
|
||||
return [
|
||||
{
|
||||
trigger: ".o_main_navbar span:contains('Products')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains('Products')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `.o_kanban_record:contains("${name}")`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `.o_form_renderer`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { negateStep } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
export function checkCashMoveShown(amount) {
|
||||
return {
|
||||
content: `Check has cash move with amount ${amount}`,
|
||||
trigger: `.cash-move-list .cash-move-row .cash-move-amount:contains(${amount})`,
|
||||
};
|
||||
}
|
||||
export function noCashMoveDeleteButton() {
|
||||
return [
|
||||
negateStep({
|
||||
content: `Check that the delete button is not present`,
|
||||
trigger: `.cash-move-list .cash-move-row .delete-row`,
|
||||
}),
|
||||
];
|
||||
}
|
||||
export function deleteCashMove(amount) {
|
||||
return [
|
||||
{
|
||||
content: `Delete cash move with amount ${amount}`,
|
||||
trigger: `.cash-move-list .cash-move-row:contains(${amount}) .delete-row .btn`,
|
||||
run: "click",
|
||||
},
|
||||
negateStep(checkCashMoveShown(amount)),
|
||||
];
|
||||
}
|
||||
export function checkNumberOfRows(number) {
|
||||
return {
|
||||
content: "check number of cash moves",
|
||||
trigger: ".cash-move-list .cash-move-row",
|
||||
run: () => {
|
||||
const cashMoveRows = document.querySelectorAll(".cash-move-list .cash-move-row").length;
|
||||
if (cashMoveRows !== number) {
|
||||
throw new Error(`Expected ${number} cash moves, found ${cashMoveRows}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
export function checkCashMoveDateTime() {
|
||||
const date = "Today";
|
||||
const time = "11:09";
|
||||
return {
|
||||
content: `Check has cash move with Date: ${date} and Time: ${time}`,
|
||||
trigger: `.cash-move-list .cash-move-row:has(.cash-move-date:contains(${date})):has(.cash-move-time:contains(${time}))`,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
/* global posmodel */
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
const { DateTime } = luxon;
|
||||
|
||||
export function confirmPopup() {
|
||||
return [Dialog.confirm()];
|
||||
}
|
||||
export function clickMenuButton() {
|
||||
return {
|
||||
content: "Click on the menu button",
|
||||
trigger: ".pos-rightheader button:has(.fa-bars)",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function clickMenuOption(name, options) {
|
||||
return [
|
||||
waitForMenuButtons(),
|
||||
clickMenuButton(),
|
||||
waitForMenuOptionsToOpen(),
|
||||
clickMenuDropdownOption(name, options),
|
||||
];
|
||||
}
|
||||
export function waitForMenuButtons() {
|
||||
return {
|
||||
content: "Wait for the menu buttons to be available",
|
||||
trigger: ".pos-rightheader button:has(.fa-bars)",
|
||||
};
|
||||
}
|
||||
export function waitForMenuOptionsToOpen() {
|
||||
return {
|
||||
content: `Wait for the menu options to be available`,
|
||||
trigger: `span.dropdown-item`,
|
||||
};
|
||||
}
|
||||
export function clickMenuDropdownOption(name, { expectUnloadPage = false } = {}) {
|
||||
return {
|
||||
content: `click on something in the burger menu`,
|
||||
trigger: `span.dropdown-item:contains(${name})`,
|
||||
run: "click",
|
||||
expectUnloadPage,
|
||||
};
|
||||
}
|
||||
export function existMenuOption(name) {
|
||||
return [
|
||||
clickMenuButton(),
|
||||
{
|
||||
content: `check that ${name} exists in the burger menu`,
|
||||
trigger: `span.dropdown-item:contains(${name})`,
|
||||
},
|
||||
clickMenuButton(),
|
||||
];
|
||||
}
|
||||
export function notExistMenuOption(name) {
|
||||
return [
|
||||
clickMenuButton(),
|
||||
{
|
||||
content: `check that ${name} doesn't exist in the burger menu`,
|
||||
trigger: negate(`span.dropdown-item:contains(${name})`),
|
||||
},
|
||||
];
|
||||
}
|
||||
export function isCashMoveButtonHidden() {
|
||||
return [
|
||||
{
|
||||
trigger: ".pos-topheader:not(:contains(Cash In/Out))",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function doCashMove(amount, reason) {
|
||||
const numpadWrite = (val) => val.split("").flatMap((key) => Numpad.click(key));
|
||||
return [
|
||||
...clickMenuOption("Cash In/Out"),
|
||||
fillTextArea(".cash-reason", reason),
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
content: "Enter the amount to cash in/out",
|
||||
trigger: ".modal input.o_input",
|
||||
run: "edit " + amount,
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: "Enter the amount to cash in/out",
|
||||
trigger: ".modal input.o_input",
|
||||
run: "click",
|
||||
},
|
||||
...numpadWrite(amount).map((step) => ({
|
||||
isActive: ["mobile"],
|
||||
...step,
|
||||
})),
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
trigger: ".o-overlay-item:nth-child(2) .modal-footer button:contains('Confirm')",
|
||||
run: "click",
|
||||
},
|
||||
Dialog.confirm(),
|
||||
];
|
||||
}
|
||||
export function endTour() {
|
||||
return {
|
||||
content: "Last tour step that avoids error mentioned in commit 443c209",
|
||||
trigger: "body",
|
||||
};
|
||||
}
|
||||
export function isSyncStatusConnected() {
|
||||
return {
|
||||
trigger: negate(".oe_status", ".pos-rightheader .status-buttons"),
|
||||
};
|
||||
}
|
||||
export function clickPlanButton() {
|
||||
return [
|
||||
{
|
||||
content: "go back to the floor screen",
|
||||
trigger: ".pos-leftheader .table-button",
|
||||
run: "click",
|
||||
},
|
||||
...waitRequest(),
|
||||
];
|
||||
}
|
||||
export function startPoS() {
|
||||
return [
|
||||
{
|
||||
content: "Start PoS",
|
||||
trigger: ".screen-login .btn.open-register-btn",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickBtn(name, { expectUnloadPage = false } = {}) {
|
||||
return {
|
||||
content: `Click on ${name}`,
|
||||
trigger: `body button:contains(${name})`,
|
||||
run: "click",
|
||||
expectUnloadPage,
|
||||
};
|
||||
}
|
||||
export function hasBtn(name) {
|
||||
return {
|
||||
content: `Check button ${name} exist`,
|
||||
trigger: `body button:contains(${name})`,
|
||||
};
|
||||
}
|
||||
export function fillTextArea(target, value) {
|
||||
return {
|
||||
content: `Fill text area with ${value}`,
|
||||
trigger: `textarea${target}`,
|
||||
run: `edit ${value}`,
|
||||
};
|
||||
}
|
||||
export function createFloatingOrder() {
|
||||
return { trigger: ".pos-leftheader .list-plus-btn", run: "click" };
|
||||
}
|
||||
|
||||
function _hasFloatingOrder(name, yes) {
|
||||
const negateIfNecessary = (trigger) => (yes ? trigger : negate(trigger));
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
trigger: negateIfNecessary(
|
||||
`.pos-topheader .floating-order-container:contains('${name}')`
|
||||
),
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
trigger: ".pos-leftheader button.fa-caret-down",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
trigger: negateIfNecessary(
|
||||
`.modal-header:contains(Choose an order) ~ .modal-body .floating-order-container:contains('${name}')`
|
||||
),
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
trigger: ".oi-arrow-left",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function hasFloatingOrder(name) {
|
||||
return _hasFloatingOrder(name, true);
|
||||
}
|
||||
|
||||
export function noFloatingOrder(name) {
|
||||
return _hasFloatingOrder(name, false);
|
||||
}
|
||||
export function clickOrders() {
|
||||
return { trigger: ".pos-leftheader .orders-button", run: "click" };
|
||||
}
|
||||
export function selectPresetTimingSlotHour(hour) {
|
||||
return [
|
||||
{
|
||||
content: `Click on the slot hour ${hour} in the modal`,
|
||||
trigger: `.modal:has(.modal-header:contains(select a preset)) button:contains('${hour}')`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: `Wait the slot hour ${hour} is set and loading is done (to avoid currency error)`,
|
||||
trigger: `body:not(:has(.modal)):not(:has(.oe_status .fa-spin)) .pos-leftheader .preset-time-btn:contains(${hour})`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function presetTimingSlotIs(hour) {
|
||||
return { trigger: `.pos-leftheader .preset-time-btn:contains('${hour}')` };
|
||||
}
|
||||
export function selectPresetTimingSlot(slot) {
|
||||
return { trigger: `.modal button:contains('${slot}')`, run: "click" };
|
||||
}
|
||||
export function presetTimingSlotHourNotExists(hour) {
|
||||
return { trigger: negate(`.modal button:visible:contains('${hour}')`) };
|
||||
}
|
||||
export function presetTimingSlotHourExists(hour) {
|
||||
return { trigger: `.modal button:contains('${hour}')` };
|
||||
}
|
||||
export function selectSlotDays(d) {
|
||||
return {
|
||||
trigger: `.modal .d-flex.w-100.flex-wrap.gap-2.mt-2 button:nth-of-type(${d})`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function selectPresetTimingSlotIndex(index) {
|
||||
return {
|
||||
trigger: `.modal .row div:not(.d-none) .d-flex.flex-wrap.gap-1 button:nth-of-type(${index})`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function clickRegister() {
|
||||
return { trigger: ".pos-leftheader .register-label", run: "click" };
|
||||
}
|
||||
export function waitRequest() {
|
||||
return [
|
||||
{
|
||||
trigger: "body",
|
||||
content: "Wait loading is finished if it is shown",
|
||||
timeout: 15000,
|
||||
async run({ waitFor }) {
|
||||
let isLoading = false;
|
||||
try {
|
||||
isLoading = await waitFor("body:has(.fa-circle-o-notch)", { timeout: 2000 });
|
||||
} catch {
|
||||
/* fa-circle-o-notch will certainly never appears :'( */
|
||||
}
|
||||
if (isLoading) {
|
||||
await waitFor("body:not(:has(.fa-circle-o-notch))", { timeout: 10000 });
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function storedOrderCount(expectedCount) {
|
||||
return {
|
||||
content: `Stored order count should be ${expectedCount}`,
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
const actualCount = posmodel.data.models["pos.order"].length;
|
||||
if (actualCount !== expectedCount) {
|
||||
throw new Error(
|
||||
`Expected stored order count to be ${expectedCount}, but got ${actualCount}`
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function isSynced() {
|
||||
return {
|
||||
content: "Check if the request is proceeded",
|
||||
trigger: negate(".fa-spin", ".status-buttons"),
|
||||
};
|
||||
}
|
||||
|
||||
export function clickOnScanButton() {
|
||||
return {
|
||||
content: "Click the Scan button located in the top header.",
|
||||
trigger: ".pos-topheader .status-buttons .fa-barcode",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
||||
export function ClickOnCustomerDisplayButton() {
|
||||
return {
|
||||
content: "Click on the customer display button inside the burger menu",
|
||||
trigger: "span i.fa-desktop",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function CustomerDisplayHasThisDeviceButton() {
|
||||
return {
|
||||
isActive: ["desktop"],
|
||||
content: "Check that the customer display popup has a 'This device' button",
|
||||
trigger: ".o_dialog .modal-body .container .btn-primary:contains('This device')",
|
||||
};
|
||||
}
|
||||
export function CustomerDisplayHasQRButton() {
|
||||
return {
|
||||
isActive: ["desktop"],
|
||||
content: "Check that the customer display popup has a 'Display QR' button",
|
||||
trigger: ".o_dialog .modal-body .container .btn-secondary:contains('Display QR')",
|
||||
};
|
||||
}
|
||||
export function ClickCustomerDisplayThisDeviceButton() {
|
||||
return {
|
||||
isActive: ["desktop"],
|
||||
content: "Check that the customer display popup has a 'This device' button",
|
||||
trigger: ".btn-primary:contains('This device')",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function ClickCustomerDisplayQRButton() {
|
||||
return {
|
||||
isActive: ["desktop"],
|
||||
content: "Check that the customer display popup has a 'Display QR' button",
|
||||
trigger: ".btn-secondary:contains('Display QR')",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function CustomerDisplayQRIsDisplayed() {
|
||||
return {
|
||||
isActive: ["desktop"],
|
||||
content: "Check that the QR code is displayed on screen",
|
||||
trigger: ".o-overlay-item .modal .modal-body img.square",
|
||||
};
|
||||
}
|
||||
export function freezeDateTime(millis) {
|
||||
return [
|
||||
{
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
DateTime.now = () => DateTime.fromMillis(millis);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const originalNow = DateTime.now;
|
||||
|
||||
export function withTimeFreeze(millis, steps) {
|
||||
return [
|
||||
{
|
||||
content: `Freeze time to ${millis}`,
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
sessionStorage.setItem("pos_test_frozen_time", millis);
|
||||
DateTime.now = () => DateTime.fromMillis(millis);
|
||||
},
|
||||
},
|
||||
...steps,
|
||||
{
|
||||
content: "Unfreeze time",
|
||||
trigger: "body",
|
||||
run: () => {
|
||||
sessionStorage.removeItem("pos_test_frozen_time");
|
||||
DateTime.now = originalNow;
|
||||
},
|
||||
},
|
||||
].flat();
|
||||
}
|
||||
|
||||
if (sessionStorage.getItem("pos_test_frozen_time")) {
|
||||
const millis = parseInt(sessionStorage.getItem("pos_test_frozen_time"));
|
||||
DateTime.now = () => DateTime.fromMillis(millis);
|
||||
}
|
||||
|
||||
export function selectPresetDateButton(formattedDate) {
|
||||
return {
|
||||
trigger: `.modal-body button:contains("${formattedDate}")`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
const productTrigger = (productName) =>
|
||||
`article.product:has(.product-name:contains("${productName}"))`;
|
||||
const isComboSelectedTrigger = (productName) =>
|
||||
`label.combo-item.selected ${productTrigger(productName)}`;
|
||||
const confirmationButtonTrigger = `footer button.confirm`;
|
||||
|
||||
export function select(productName) {
|
||||
return {
|
||||
content: `Select combo item ${productName}`,
|
||||
trigger: `.modal label.combo-item ${productTrigger(productName)}`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function isSelected(productName) {
|
||||
return {
|
||||
content: `Check that ${productName} is selected`,
|
||||
trigger: `.modal ${isComboSelectedTrigger(productName)}`,
|
||||
};
|
||||
}
|
||||
export function isNotSelected(productName) {
|
||||
return {
|
||||
content: `Check that ${productName} is not selected`,
|
||||
trigger: `.modal ${negate(isComboSelectedTrigger(productName), ".modal-body")}`,
|
||||
};
|
||||
}
|
||||
export function isConfirmationButtonDisabled() {
|
||||
return {
|
||||
content: "try to click `confirm` without having made all the selections",
|
||||
trigger: `.modal ${confirmationButtonTrigger}[disabled]`,
|
||||
};
|
||||
}
|
||||
export function checkTotal(expectedAmount) {
|
||||
return {
|
||||
content: `Check that combo total amount is $${expectedAmount}`,
|
||||
trigger: `.modal div.h3:contains("Total: $ ${expectedAmount}")`,
|
||||
};
|
||||
}
|
||||
export function clickQtyBtnAdd(productName) {
|
||||
return {
|
||||
content: `Click the add quantity button for ${productName}`,
|
||||
trigger: `.modal article:has(.product-name:contains("${productName}")) button[name="pos_quantity_button_plus"]`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function clickQtyBtnMinus(productName) {
|
||||
return {
|
||||
content: `Click the minus quantity button for ${productName}`,
|
||||
trigger: `.modal article:has(.product-name:contains("${productName}")) button[name="pos_quantity_button_minus"]`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function checkProductQty(productName, expectedQty) {
|
||||
return {
|
||||
content: `Check that product ${productName} has quantity ${expectedQty}`,
|
||||
trigger: `.modal article:has(.product-name:contains("${productName}")):has(input[name="pos_quantity"])`,
|
||||
run: () => {
|
||||
const article = [...document.querySelectorAll(".modal article")].find((el) =>
|
||||
el.textContent.includes(productName)
|
||||
);
|
||||
const input = article.querySelector('input[name="pos_quantity"]');
|
||||
if (input.value != expectedQty) {
|
||||
throw new Error(
|
||||
`Expected ${expectedQty}, but got ${input.value} for "${productName}".`
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
export function checkImgAndSelect(productName, checkImg = false) {
|
||||
const productArticleSelector = productTrigger(productName);
|
||||
const withImg = `${productArticleSelector}:has(.product-img)`;
|
||||
const withoutImg = `${productArticleSelector}:not(:has(.product-img))`;
|
||||
const trigger = `.modal ${checkImg ? withImg : withoutImg}`;
|
||||
return {
|
||||
content: `Check image & select combo item ${productName}`,
|
||||
trigger: trigger,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
export function back() {
|
||||
return {
|
||||
content: "go back to the products",
|
||||
trigger: ".actionpad .back-button",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
||||
export function inLeftSide(steps) {
|
||||
return [
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: "click review button",
|
||||
trigger: ".btn-switchpane.review-button",
|
||||
run: "click",
|
||||
},
|
||||
...[steps].flat(),
|
||||
{ ...back(), isActive: ["mobile"] },
|
||||
];
|
||||
}
|
||||
|
||||
export function waitForLoading() {
|
||||
return [
|
||||
{
|
||||
content: "waiting for loading to finish",
|
||||
trigger: "body:not(:has(.loader))",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function selectButton(name) {
|
||||
return {
|
||||
content: `Select button ${name}`,
|
||||
trigger: `button:contains("${name}")`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export function isShown() {
|
||||
return {
|
||||
content: "feedback screen is shown",
|
||||
trigger: ".feedback-screen",
|
||||
};
|
||||
}
|
||||
|
||||
export function clickScreen() {
|
||||
return {
|
||||
content: "click on feedback screen",
|
||||
trigger: ".feedback-screen",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export class GenericHooks {
|
||||
static afterValidateHook() {
|
||||
// This function can be overridden in the localization to add steps after payment validation
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import * as ProductConfigurator from "@point_of_sale/../tests/pos/tours/utils/product_configurator_util";
|
||||
|
||||
export function addOptionalProduct(productName, quantity, configurable) {
|
||||
const step = [
|
||||
// Verify that the optional product is visible in the list
|
||||
{
|
||||
content: `Verify that the optional product "${productName}" is available in the list.`,
|
||||
trigger: `.optional-product-line .product-name:contains("${productName}")`,
|
||||
},
|
||||
{
|
||||
content: `Click the "+ Add" button to add the optional product "${productName}" to the cart.`,
|
||||
trigger: `.optional-product-line .cart-buttons button:contains("+ Add")`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
|
||||
// Handle configuration steps for configurable optional products
|
||||
if (configurable) {
|
||||
step.push(
|
||||
// Choose the color attribute for the configurable product
|
||||
...ProductConfigurator.pickColor("Blue"),
|
||||
// Select the material type from dropdown options
|
||||
...ProductConfigurator.pickSelect("Metal"),
|
||||
// Choose the texture or fabric type via radio buttons
|
||||
...ProductConfigurator.pickRadio("wool"),
|
||||
// confirm Attribute Selection dialogue
|
||||
{
|
||||
trigger: ".o-overlay-item:nth-child(2) .modal-footer button:contains('Add')",
|
||||
run: "click",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (quantity > 1) {
|
||||
for (let i = 1; i < quantity; i++) {
|
||||
// Increment the product quantity by clicking the "+" button
|
||||
step.push(
|
||||
{
|
||||
content: `Verify the quantity of "${productName}" is updated to ${i}.`,
|
||||
trigger: `.optional-product-line .cart-buttons input:value("${i}")`,
|
||||
},
|
||||
{
|
||||
content: `Increase the quantity of "${productName}" by clicking the "+" button.`,
|
||||
trigger: `.optional-product-line .cart-buttons button:eq(1)`,
|
||||
run: "click",
|
||||
}
|
||||
);
|
||||
}
|
||||
step.push({
|
||||
content: `Click the "Add" button to confirm adding "${productName}" to the order.`,
|
||||
trigger: `.modal-footer button:contains("Add")`,
|
||||
run: "click",
|
||||
});
|
||||
|
||||
return step;
|
||||
}
|
||||
}
|
||||
|
||||
export function checkImage(productName, shouldHaveImage = false) {
|
||||
const baseSelector = `.modal .optional-product-line:has(.product-name:contains("${productName}"))`;
|
||||
const trigger = shouldHaveImage
|
||||
? `${baseSelector}:has(img.product-img)`
|
||||
: `${baseSelector}:not(:has(img.product-img))`;
|
||||
|
||||
return {
|
||||
content: `Check image visibility for optional product "${productName}"`,
|
||||
trigger,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
import { negateStep } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
export function clickPartner(name = "", { expectUnloadPage = false } = {}) {
|
||||
return {
|
||||
content: `click partner '${name}' from partner list screen`,
|
||||
trigger: `.modal .partner-list b:contains(${name})`,
|
||||
run: "click",
|
||||
expectUnloadPage,
|
||||
};
|
||||
}
|
||||
export function clickPartnerOptions(name) {
|
||||
return {
|
||||
content: `click partner from partner list screen`,
|
||||
trigger: `.partner-info:contains("${name}") button.dropdown`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
||||
export function checkDropDownItemText(text) {
|
||||
return {
|
||||
content: `check for dropdown item containing text`,
|
||||
trigger: `.o-dropdown-item:contains("${text}")`,
|
||||
};
|
||||
}
|
||||
|
||||
export function clickDropDownItemText(text) {
|
||||
return {
|
||||
content: `click for dropdown item containing text`,
|
||||
trigger: `.o-dropdown-item:contains("${text}")`,
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
|
||||
export function clickSettleOrderName(
|
||||
prefix,
|
||||
suffix = "",
|
||||
checkCurrentYear = false,
|
||||
availability = true
|
||||
) {
|
||||
let trigger = `tr.o_data_row td[name='name']:contains("${prefix}")`;
|
||||
if (checkCurrentYear) {
|
||||
trigger += `:contains("${new Date().getFullYear()}")`;
|
||||
}
|
||||
if (suffix) {
|
||||
trigger += `:contains("${suffix}")`;
|
||||
}
|
||||
const step = {
|
||||
content: "Check the settle due account line is present",
|
||||
trigger,
|
||||
run: "click",
|
||||
};
|
||||
if (!availability) {
|
||||
return negateStep(step);
|
||||
}
|
||||
return step;
|
||||
}
|
||||
|
||||
export function settleCustomerAccount(
|
||||
partner,
|
||||
dueAmount,
|
||||
orderPrefix,
|
||||
orderSuffix = "",
|
||||
checkYear = false,
|
||||
orderSettlement = false,
|
||||
availability = true
|
||||
) {
|
||||
const steps = [
|
||||
{
|
||||
trigger: `tr:contains(${partner}) .partner-due:contains(${dueAmount})`,
|
||||
},
|
||||
clickPartnerOptions(`${partner}`),
|
||||
];
|
||||
const buttonText = orderSettlement ? "Settle orders" : "Settle invoices";
|
||||
steps.push(
|
||||
...[
|
||||
clickDropDownItemText(buttonText),
|
||||
clickSettleOrderName(orderPrefix, orderSuffix, checkYear, availability),
|
||||
]
|
||||
);
|
||||
return steps;
|
||||
}
|
||||
|
||||
export function checkContactValues(name, address = "", phone = "", email = "") {
|
||||
const steps = [
|
||||
{
|
||||
content: `Check partner "${name}" from partner list screen`,
|
||||
trigger: `.partner-list .partner-info:contains("${name}")`,
|
||||
},
|
||||
{
|
||||
content: `Check address "${address}" for partner "${name}"`,
|
||||
trigger: `.partner-list .partner-info:contains("${name}") .partner-line-adress:contains("${address}")`,
|
||||
},
|
||||
];
|
||||
|
||||
if (phone) {
|
||||
steps.push({
|
||||
content: `Check phone number "${phone}" for partner "${name}"`,
|
||||
trigger: `.partner-list .partner-info:contains("${name}") .partner-line-email:contains("${phone}")`,
|
||||
});
|
||||
}
|
||||
|
||||
if (email) {
|
||||
steps.push({
|
||||
content: `Check email address "${email}" for partner "${name}"`,
|
||||
trigger: `.partner-list .partner-info:contains("${name}") .partner-line-email .email-field:contains("${email}")`,
|
||||
});
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
export function checkCustomerShown(val) {
|
||||
return {
|
||||
content: `Check "${val}" is shown`,
|
||||
trigger: `.partner-list .partner-info:nth-child(1):contains("${val}")`,
|
||||
};
|
||||
}
|
||||
|
||||
export function searchCustomerValue(val, pressEnter = false) {
|
||||
const steps = [
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: `Click search field`,
|
||||
trigger: `.modal-dialog .fa-search.undefined`,
|
||||
run: `click`,
|
||||
},
|
||||
{
|
||||
content: `Search customer with "${val}"`,
|
||||
trigger: `.modal-dialog .input-group input`,
|
||||
run: `edit ${val}`,
|
||||
},
|
||||
];
|
||||
|
||||
if (pressEnter) {
|
||||
steps.push({
|
||||
content: `Manually trigger keyup event`,
|
||||
trigger: ".modal-header .input-group input",
|
||||
run: function () {
|
||||
document
|
||||
.querySelector(".modal-header .input-group input")
|
||||
.dispatchEvent(new KeyboardEvent("keyup", { key: "" }));
|
||||
},
|
||||
});
|
||||
steps.push({
|
||||
content: `Press Enter to trigger "search more"`,
|
||||
trigger: `.modal-dialog .input-group input`,
|
||||
run: function () {
|
||||
document
|
||||
.querySelector(".modal-dialog .input-group input")
|
||||
.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "Enter" }));
|
||||
},
|
||||
});
|
||||
}
|
||||
steps.push(checkCustomerShown(val));
|
||||
return steps;
|
||||
}
|
||||
|
||||
export function scrollBottom() {
|
||||
return {
|
||||
content: `Scroll to the bottom of the partner list`,
|
||||
trigger: `.modal-body.partner-list`,
|
||||
run: () => {
|
||||
const partnerList = document.querySelector(".modal-body.partner-list");
|
||||
partnerList.scrollTop = partnerList.scrollHeight;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function isShown() {
|
||||
return [
|
||||
{
|
||||
content: "partner list screen is shown",
|
||||
trigger: ".modal .partner-list",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
/* global posmodel */
|
||||
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as PartnerList from "@point_of_sale/../tests/pos/tours/utils/partner_list_util";
|
||||
import * as NumberPopup from "@point_of_sale/../tests/generic_helpers/number_popup_util";
|
||||
|
||||
/**
|
||||
* Clicks on the payment method and then performs checks if necessary.
|
||||
*
|
||||
* @param {string} name - The name of the payment method to click on. This name is used to identify the corresponding element in the user interface.
|
||||
* @param {boolean} [isCheckNeeded=false] - Indicates whether additional checks are necessary after clicking on the payment method. If `true`, additional verification steps will be added to ensure that the expected changes (such as the remaining amount, change, or selected amount) are correctly applied.
|
||||
* @param {Object} [options={}] - An object containing additional options for the checks. The options include:
|
||||
* @param {string|null} [options.remaining=null] - The expected remaining amount after selecting the payment method. If provided and `isCheckNeeded` is `true`, a check will be performed to ensure this remaining amount is correct.
|
||||
* @param {string|null} [options.change=null] - The expected change amount after selecting the payment method. If provided and `isCheckNeeded` is `true`, a check will be performed to confirm this change amount.
|
||||
* @param {string|null} [options.amount=null] - The specific amount associated with the selected payment method. If provided and `isCheckNeeded` is `true`, a check will ensure that the selected amount is correctly displayed.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // Clicks on the "Cash" payment method without additional checks
|
||||
* clickPaymentMethod("Cash");
|
||||
*
|
||||
* // Clicks on the "Bank" payment method and checks the remaining amount and change
|
||||
* clickPaymentMethod("Cash", true, { remaining: "50.20", change: "10.50" });
|
||||
*
|
||||
* // Clicks on the "Cash" payment method and checks the amount to be paid
|
||||
* clickPaymentMethod("Cash", true, { amount: "10.20" });
|
||||
*/
|
||||
export function clickPaymentMethod(name, isCheckNeeded = false, options = {}) {
|
||||
const { remaining = null, change = null, amount = null } = options;
|
||||
|
||||
const step = [
|
||||
{
|
||||
content: `click '${name}' payment method`,
|
||||
trigger: `.paymentmethods .button.paymentmethod .payment-name:contains("${name}")`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
|
||||
if (isCheckNeeded) {
|
||||
if (remaining) {
|
||||
step.push(...remainingIs(remaining));
|
||||
}
|
||||
if (change) {
|
||||
step.push(...changeIs(change));
|
||||
}
|
||||
if (amount) {
|
||||
step.push(...selectedPaymentlineHas(name, amount));
|
||||
}
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
/**
|
||||
* Delete the paymentline having the given payment method name and amount.
|
||||
* @param {String} name payment method
|
||||
* @param {String} amount
|
||||
*/
|
||||
export function clickPaymentlineDelButton(name, amount, mobile = false) {
|
||||
return [
|
||||
{
|
||||
content: `delete ${name} paymentline with ${amount} amount`,
|
||||
trigger: `.paymentlines .paymentline .payment-infos:contains("${name}"):has(.payment-amount:contains("${amount}")) ~ .delete-button`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickCancelButton() {
|
||||
return [
|
||||
{
|
||||
content: "Cancel the ongoing payment request currently being processed.",
|
||||
trigger: ".paymentlines .paymentline .send_payment_cancel",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickRetryButton() {
|
||||
return [
|
||||
{
|
||||
content: "Retry sending the payment request using the payment terminal.",
|
||||
trigger: ".paymentlines .paymentline .send_payment_request:contains('Retry')",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickRefundButton() {
|
||||
return [
|
||||
{
|
||||
content: "Initiate a refund request for the selected order.",
|
||||
trigger: ".paymentlines .send_refund_request:contains('Refund')",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Click the paymentline having the given payment method name and amount.
|
||||
* @param {String} name payment method
|
||||
* @param {String} amount
|
||||
*/
|
||||
export function clickPaymentline(name, amount) {
|
||||
return [
|
||||
{
|
||||
content: `click ${name} paymentline with ${amount} amount`,
|
||||
trigger: `.paymentlines .paymentline .payment-infos:contains("${name}"):has(.payment-amount:contains("${amount}"))`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickInvoiceButton() {
|
||||
return [
|
||||
{
|
||||
content: "click invoice button",
|
||||
trigger: ".payment-buttons .js_invoice",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickValidate() {
|
||||
return [
|
||||
{
|
||||
content: "validate payment",
|
||||
trigger: `.payment-screen button.validation-button.next`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Press the numpad in sequence based on the given space-separated keys.
|
||||
* Note: Maximum of 2 characters because NumberBuffer only allows 2 consecutive
|
||||
* fast inputs. Fast inputs is the case in tours. This method is only for the
|
||||
* desktop environment. The mobile environment doesn't work exactly the same way
|
||||
* so we have to call fillPaymentLineAmountMobile to have the same behaviour.
|
||||
*
|
||||
* e.g. :
|
||||
* PaymentScreen.enterPaymentLineAmount("Cash", "70"),
|
||||
* PaymentScreen.remainingIs("2.0"),
|
||||
* PaymentScreen.clickNumpad("0"), <- desktop: add a 0
|
||||
* PaymentScreen.fillPaymentLineAmountMobile("Cash", "700"), <- mobile: rewrite the amount
|
||||
* PaymentScreen.remainingIs("0.00"),
|
||||
* PaymentScreen.changeIs("628.0"),
|
||||
*
|
||||
* @param {String} keys space-separated numpad keys
|
||||
*/
|
||||
export function clickNumpad(keys) {
|
||||
return keys.split(" ").map((key) => ({ ...Numpad.click(key), isActive: ["desktop"] }));
|
||||
}
|
||||
export function clickBack() {
|
||||
return [
|
||||
{
|
||||
content: "click back button",
|
||||
trigger: ".back-button",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickBackToProductScreen() {
|
||||
return [
|
||||
{
|
||||
content: "click back to product screen",
|
||||
trigger: ".payment-screen .back-button",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickTipButton() {
|
||||
return [
|
||||
{
|
||||
trigger: ".payment-screen .button:contains('Tip')",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Enter an amount for a specified payment line and then perform checks if necessary.
|
||||
*
|
||||
* This function performs the entry of an amount on a payment line in the user interface. It can also check for expected conditions such as the remaining amount, change, or the selected amount after the entry.
|
||||
*
|
||||
* @param {string} lineName - The name of the payment line where the amount needs to be entered. This name helps to identify the target payment line in the user interface.
|
||||
* @param {string} keys - The sequence of keys to simulate for the amount entry, in the form of a string where each character represents a key to press.
|
||||
* @param {boolean} [isCheckNeeded=false] - Indicates whether additional checks need to be performed after the amount entry.
|
||||
* @param {Object} [options={}] - An object containing additional options for checks. The options include:
|
||||
* @param {string|null} [options.remaining=null] - The expected remaining amount after the amount is entered on the payment line. If provided and `isCheckNeeded` is `true`, a check will be performed to ensure this remaining amount is correct.
|
||||
* @param {string|null} [options.change=null] - The expected change amount after the amount is entered on the payment line. If provided and `isCheckNeeded` is `true`, a check will be performed to confirm this change amount.
|
||||
* @param {string|null} [options.amount=null] - The specific amount expected on the payment for this line after the entry. If provided and `isCheckNeeded` is `true`, a check will ensure that the selected amount is correctly displayed.
|
||||
*
|
||||
* @example
|
||||
* // Enter the amount "50" on the "Cash" payment line without additional checks
|
||||
* enterPaymentLineAmount("Cash", "50");
|
||||
*
|
||||
* @example
|
||||
* // Enter the amount "100" on the "Bank" payment line and check that the remaining amount is 50 and the change is 20
|
||||
* enterPaymentLineAmount("Bank", "100", true, { remaining: "50.0", change: "20.0" });
|
||||
*/
|
||||
export function enterPaymentLineAmount(lineName, keys, isCheckNeeded = false, options = {}) {
|
||||
const { remaining = null, change = null, amount = null } = options;
|
||||
const step = [
|
||||
...clickNumpad(keys.split("").join(" ")),
|
||||
...fillPaymentLineAmountMobile(lineName, keys),
|
||||
];
|
||||
|
||||
if (isCheckNeeded) {
|
||||
if (remaining) {
|
||||
step.push(...remainingIs(remaining));
|
||||
}
|
||||
if (change) {
|
||||
step.push(...changeIs(change));
|
||||
}
|
||||
if (amount) {
|
||||
step.push(...selectedPaymentlineHas(lineName, amount));
|
||||
}
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
export function fillPaymentLineAmountMobile(lineName, keys) {
|
||||
return [
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: "click payment line",
|
||||
trigger: `.paymentlines .paymentline .payment-infos:contains("${lineName}")`,
|
||||
run: "click",
|
||||
},
|
||||
...NumberPopup.enterValue(keys).map((step) => ({
|
||||
...step,
|
||||
isActive: ["mobile"],
|
||||
run: "click",
|
||||
})),
|
||||
{
|
||||
...Dialog.confirm(),
|
||||
isActive: ["mobile"],
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isShown() {
|
||||
return [
|
||||
{
|
||||
content: "payment screen is shown",
|
||||
trigger: ".pos .payment-screen",
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if change is the provided amount.
|
||||
* @param {String} amount
|
||||
*/
|
||||
export function changeIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `change is ${amount}`,
|
||||
trigger: `.payment-status-amount .amount:contains("${amount}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function isInvoiceOptionSelected() {
|
||||
return [
|
||||
{
|
||||
content: "Invoice option is selected",
|
||||
trigger: ".payment-buttons .js_invoice.highlight",
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if the remaining is the provided amount.
|
||||
* @param {String} amount
|
||||
*/
|
||||
export function remainingIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `remaining amount is ${amount}`,
|
||||
trigger: `.payment-status-amount .amount:contains("${amount}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if validate button is highlighted.
|
||||
* @param {Boolean} isHighlighted
|
||||
*/
|
||||
export function validateButtonIsHighlighted(isHighlighted = true) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
content: `validate button is ${isHighlighted ? "highlighted" : "not highlighted"}`,
|
||||
trigger: isHighlighted
|
||||
? `.payment-screen button.validation-button.next.highlight`
|
||||
: `.payment-screen button.validation-button.next:not(:has(.highlight))`,
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if the paymentlines are empty. Also provide the amount to pay.
|
||||
* @param {String} amountToPay
|
||||
*/
|
||||
export function emptyPaymentlines(amountToPay) {
|
||||
return [
|
||||
{
|
||||
content: `there are no paymentlines`,
|
||||
trigger: `.paymentlines-empty`,
|
||||
},
|
||||
{
|
||||
content: `amount to pay is '${amountToPay}'`,
|
||||
trigger: `.paymentlines-empty .total:contains("${amountToPay}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if the selected paymentline has the given payment method and amount.
|
||||
* @param {String} paymentMethodName
|
||||
* @param {String} amount
|
||||
*/
|
||||
export function selectedPaymentlineHas(paymentMethodName, amount) {
|
||||
return [
|
||||
{
|
||||
content: `line paid via '${paymentMethodName}' is selected`,
|
||||
trigger: `.paymentlines .paymentline.selected .payment-name:contains("${paymentMethodName}")`,
|
||||
},
|
||||
{
|
||||
content: `amount tendered in the line is '${amount}'`,
|
||||
trigger: `.paymentlines .paymentline.selected .payment-amount:contains("${amount}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function totalIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `total is ${amount}`,
|
||||
trigger: `.total:contains("${amount}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function pay(method, amount) {
|
||||
const steps = [];
|
||||
steps.push(...clickPaymentMethod(method));
|
||||
for (const char of amount.split("")) {
|
||||
steps.push(...clickNumpad(char));
|
||||
}
|
||||
steps.push(...validateButtonIsHighlighted());
|
||||
steps.push(...clickValidate());
|
||||
return steps;
|
||||
}
|
||||
|
||||
export function isInvoiceButtonChecked() {
|
||||
return [
|
||||
{
|
||||
content: "check invoice button is checked",
|
||||
trigger: ".js_invoice.highlight",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function clickShipLaterButton() {
|
||||
return [
|
||||
{
|
||||
content: "click ship later button",
|
||||
trigger: ".button:contains('Ship Later')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "click confirm button",
|
||||
trigger: ".btn:contains('Confirm')",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function clickPartnerButton() {
|
||||
return [
|
||||
{
|
||||
content: "click customer button",
|
||||
trigger: "button.partner-button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "partner screen is shown",
|
||||
trigger: `${PartnerList.clickPartner().trigger}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function clickCustomer(name, pressEnter = false) {
|
||||
return [...PartnerList.searchCustomerValue(name, pressEnter), PartnerList.clickPartner(name)];
|
||||
}
|
||||
|
||||
export function shippingLaterHighlighted() {
|
||||
return {
|
||||
content: "Shipping later button is highlighted",
|
||||
trigger: ".button:contains('Ship Later').highlight",
|
||||
};
|
||||
}
|
||||
|
||||
// This method is used to simulate payment with a payment terminal, before using terminal the order
|
||||
// is synced to ensure that the order is up-to-date and ready for payment.
|
||||
export function syncCurrentOrder() {
|
||||
return [
|
||||
{
|
||||
content: "sync current order",
|
||||
trigger: "body",
|
||||
run: async () => {
|
||||
const currentOrder = posmodel.getOrder();
|
||||
const order = await posmodel.syncAllOrders({ orders: [currentOrder] });
|
||||
|
||||
if (!order[0].isSynced) {
|
||||
throw new Error("Order ID is not a number after sync.");
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isInvoiceButtonUnchecked() {
|
||||
return [
|
||||
{
|
||||
content: "check invoice button is not highlighted",
|
||||
trigger: ".js_invoice:not(.highlight)",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
/* global posmodel */
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
|
||||
export async function generateReceiptsToPrint(order, orderChange) {
|
||||
const { orderData, changes } = posmodel.generateOrderChange(
|
||||
order,
|
||||
orderChange,
|
||||
Array.from(posmodel.config.printerCategories),
|
||||
false
|
||||
);
|
||||
const receiptsData = await posmodel.generateReceiptsDataToPrint(
|
||||
orderData,
|
||||
changes,
|
||||
orderChange
|
||||
);
|
||||
const groupedReceiptsData = await posmodel.prepareReceiptGroupedData(receiptsData);
|
||||
return groupedReceiptsData.map((data) =>
|
||||
renderToElement("point_of_sale.OrderChangeReceipt", {
|
||||
data: data,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Return rendered order change receipts that will be printed when clicking "Order" button
|
||||
export async function generatePreparationReceipts() {
|
||||
const order = posmodel.getOrder();
|
||||
const orderChange = posmodel.changesToOrder(order, posmodel.config.printerCategories, false);
|
||||
return await generateReceiptsToPrint(order, orderChange);
|
||||
}
|
||||
|
||||
// Return rendered fire course receipts that will be printed when clicking "Fire course" button
|
||||
export async function generateFireCourseReceipts() {
|
||||
const order = posmodel.getOrder();
|
||||
const course = order.getSelectedCourse();
|
||||
const orderChange = {
|
||||
new: [],
|
||||
cancelled: [],
|
||||
noteUpdate: course.lines.map((line) => ({ product_id: line.getProduct().id })),
|
||||
noteUpdateTitle: _t("Course %s fired", "" + course.index),
|
||||
printNoteUpdateData: false,
|
||||
};
|
||||
return await generateReceiptsToPrint(order, orderChange);
|
||||
}
|
||||
|
||||
export function checkPreparationTicketData(
|
||||
data,
|
||||
opts = {
|
||||
visibleInDom: [],
|
||||
invisibleInDom: [],
|
||||
lineOrder: [],
|
||||
fireCourse: false,
|
||||
}
|
||||
) {
|
||||
const check = async () => {
|
||||
let tickets = [];
|
||||
|
||||
if (opts.fireCourse) {
|
||||
tickets = await generateFireCourseReceipts();
|
||||
} else {
|
||||
tickets = await generatePreparationReceipts();
|
||||
}
|
||||
|
||||
if (
|
||||
!tickets[0] &&
|
||||
!data.length &&
|
||||
!opts.invisibleInDom?.length &&
|
||||
!opts.visibleInDom?.length &&
|
||||
!opts.lineOrder?.length &&
|
||||
!opts.fireCourse
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lines = tickets[0].querySelectorAll(".orderline");
|
||||
const lineNames = [];
|
||||
|
||||
let idx = 0;
|
||||
for (const line of lines) {
|
||||
const name = line.firstChild.children[1].innerHTML;
|
||||
const qty = line.firstChild.children[0].innerHTML;
|
||||
const domAttrs = Object.values(line.children[1]?.children || []);
|
||||
const attrs = domAttrs.map((c) => c.innerHTML).filter(Boolean);
|
||||
const values = data[idx];
|
||||
|
||||
if (values.qty != qty) {
|
||||
throw new Error(
|
||||
`Ticket data mismatch for ${name}: expected ${values.qty}, got ${qty}`
|
||||
);
|
||||
}
|
||||
|
||||
if (values.name != name) {
|
||||
throw new Error(
|
||||
`Ticket data mismatch for ${name}: expected ${values.name}, got ${name}, maybe lines ordering ?`
|
||||
);
|
||||
}
|
||||
|
||||
if (values.attributes) {
|
||||
for (const attr of values.attributes) {
|
||||
const found = attrs.find((a) => a.includes(attr));
|
||||
if (!found) {
|
||||
throw new Error(
|
||||
`Attribute ${attr} not found in printed receipt for ${name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!values) {
|
||||
throw new Error(`Received ${name} but no check data found`);
|
||||
}
|
||||
|
||||
lineNames.push(name);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (opts.visibleInDom) {
|
||||
for (const inDom of opts.visibleInDom) {
|
||||
let found = false;
|
||||
for (const ticket of tickets) {
|
||||
if (ticket.innerHTML.includes(inDom)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw new Error(`${inDom} not found in printed receipt`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.invisibleInDom) {
|
||||
for (const notInDom of opts.invisibleInDom) {
|
||||
for (const ticket of tickets) {
|
||||
if (ticket.innerHTML.includes(notInDom)) {
|
||||
throw new Error(`${notInDom} should not be in printed receipt`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
trigger: "body",
|
||||
run: async () => await check(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/* global posmodel */
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw message || "Assertion failed";
|
||||
}
|
||||
}
|
||||
|
||||
function assertProductPrice(product, pricelist_name, quantity, expected_price) {
|
||||
return function () {
|
||||
var pricelist = posmodel.data.models["product.pricelist"].find(
|
||||
(pricelist) => pricelist.name === pricelist_name
|
||||
);
|
||||
var frontend_price = product.getPrice(
|
||||
pricelist,
|
||||
quantity,
|
||||
0,
|
||||
false,
|
||||
product.product_variant_ids[0]
|
||||
);
|
||||
const ProductPrice = posmodel.data.models["decimal.precision"].find(
|
||||
(dp) => dp.name === "Product Price"
|
||||
);
|
||||
frontend_price = ProductPrice.round(frontend_price);
|
||||
var diff = Math.abs(expected_price - frontend_price);
|
||||
|
||||
assert(
|
||||
diff < 0.001,
|
||||
JSON.stringify({
|
||||
product: product.id,
|
||||
product_display_name: product.display_name,
|
||||
pricelist_name: pricelist_name,
|
||||
quantity: quantity,
|
||||
}) +
|
||||
" DOESN'T MATCH -> " +
|
||||
expected_price +
|
||||
" != " +
|
||||
frontend_price
|
||||
);
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
export function setUp() {
|
||||
return [
|
||||
// The global posmodel is only present when the posmodel is instantiated
|
||||
// So, wait for everything to be loaded
|
||||
{
|
||||
content: "waiting for loading to finish",
|
||||
trigger: "body:not(:has(.pos-loader))", // Pos has finished loading
|
||||
run: function () {
|
||||
var product_wall_shelf = posmodel.data.models["product.template"]
|
||||
.getAll()
|
||||
.find((p) => p.display_name === "Wall Shelf Unit");
|
||||
var product_small_shelf = posmodel.data.models["product.template"]
|
||||
.getAll()
|
||||
.find((p) => p.display_name === "Small Shelf");
|
||||
var product_magnetic_board = posmodel.data.models["product.template"]
|
||||
.getAll()
|
||||
.find((p) => p.display_name === "Magnetic Board");
|
||||
var product_monitor_stand = posmodel.data.models["product.template"]
|
||||
.getAll()
|
||||
.find((p) => p.display_name === "Monitor Stand");
|
||||
var product_desk_pad = posmodel.data.models["product.template"]
|
||||
.getAll()
|
||||
.find((p) => p.display_name === "Desk Pad");
|
||||
var product_letter_tray = posmodel.data.models["product.template"]
|
||||
.getAll()
|
||||
.find((p) => p.display_name === "Letter Tray");
|
||||
|
||||
assertProductPrice(product_letter_tray, "Public Pricelist", 0, 4.8)()
|
||||
.then(assertProductPrice(product_letter_tray, "Public Pricelist", 1, 4.8))
|
||||
.then(assertProductPrice(product_letter_tray, "Fixed", 1, 1))
|
||||
.then(assertProductPrice(product_letter_tray, "Fixed", -1, 1))
|
||||
.then(assertProductPrice(product_wall_shelf, "Fixed", 1, 2))
|
||||
.then(assertProductPrice(product_small_shelf, "Fixed", 1, 13.95))
|
||||
.then(assertProductPrice(product_wall_shelf, "Percentage", 1, 0))
|
||||
.then(assertProductPrice(product_small_shelf, "Percentage", 1, 0.03))
|
||||
.then(assertProductPrice(product_magnetic_board, "Percentage", 1, 1.98))
|
||||
.then(assertProductPrice(product_wall_shelf, "Formula", 1, 6.86))
|
||||
.then(assertProductPrice(product_small_shelf, "Formula", 1, 2.99))
|
||||
.then(assertProductPrice(product_magnetic_board, "Formula", 1, 11.98))
|
||||
.then(assertProductPrice(product_monitor_stand, "Formula", 1, 8.19))
|
||||
.then(assertProductPrice(product_desk_pad, "Formula", 1, 6.98))
|
||||
.then(assertProductPrice(product_wall_shelf, "min_quantity ordering", 1, 2))
|
||||
.then(assertProductPrice(product_wall_shelf, "min_quantity ordering", 2, 1))
|
||||
.then(assertProductPrice(product_letter_tray, "Category vs no category", 1, 2))
|
||||
.then(assertProductPrice(product_letter_tray, "Category", 1, 2))
|
||||
.then(assertProductPrice(product_wall_shelf, "Product template", 1, 1))
|
||||
.then(assertProductPrice(product_wall_shelf, "Dates", 1, 2))
|
||||
.then(
|
||||
assertProductPrice(product_small_shelf, "Pricelist base rounding", 1, 13.95)
|
||||
)
|
||||
.then(function () {
|
||||
document.querySelector(".pos").classList.add("done-testing");
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function waitForUnitTest() {
|
||||
return [
|
||||
{
|
||||
content: "wait for unit tests to finish",
|
||||
trigger: ".pos.done-testing",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
export function pickRadio(name) {
|
||||
return [
|
||||
{
|
||||
content: `picking radio attribute with name ${name}`,
|
||||
trigger: `.modal .attribute-name-cell:contains('${name}') input`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function selectedRadio(name) {
|
||||
return [
|
||||
{
|
||||
content: `checking selected radio attribute with name ${name}`,
|
||||
trigger: `.modal .attribute-name-cell:contains('${name}') input:checked`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function pickMulti(name) {
|
||||
return [
|
||||
{
|
||||
content: `picking multi attribute with name ${name}`,
|
||||
trigger: `.modal label[for^="multi-"]:contains('${name}')`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function selectedMulti(name) {
|
||||
return [
|
||||
{
|
||||
content: `checking selected multi attribute with name ${name}`,
|
||||
trigger: `.modal label[for^="multi-"].active:contains('${name}')`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function pickSelect(name) {
|
||||
return [
|
||||
{
|
||||
content: `picking select attribute with name ${name}`,
|
||||
trigger: `.modal .configurator_select:has(option:contains('${name}'))`,
|
||||
run: ({ queryAll }) => {
|
||||
const selects = queryAll`.modal .configurator_select`;
|
||||
for (const select of selects) {
|
||||
const option = Array.from(select.options).find(
|
||||
(opt) => opt.textContent.trim() === name
|
||||
);
|
||||
if (option) {
|
||||
select.value = option.value;
|
||||
// Manually trigger change event
|
||||
select.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error(`Option "${name}" not found in any select`);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function selectedSelect(name) {
|
||||
return [
|
||||
{
|
||||
content: `check selected value for select containing option "${name}"`,
|
||||
trigger: `.modal .configurator_select:has(option:contains(${name}))`,
|
||||
run: ({ queryAll }) => {
|
||||
const selects = queryAll`.modal .configurator_select:has(option:contains(${name}))`;
|
||||
for (const select of selects) {
|
||||
const selected = select.options[select.selectedIndex];
|
||||
if (selected?.textContent.trim() === name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
throw new Error(`No select found with option "${name}" selected`);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function pickColor(name) {
|
||||
return [
|
||||
{
|
||||
content: `picking color attribute with name ${name}`,
|
||||
trigger: `.modal .configurator_color[data-color='${name}']`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function selectedColor(name) {
|
||||
return [
|
||||
{
|
||||
content: `checking selected color attribute with name ${name}`,
|
||||
trigger: `.modal .configurator_color[data-color='${name}'].active`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function fillCustomAttribute(value) {
|
||||
return [
|
||||
{
|
||||
content: `filling custom attribute with value ${value}`,
|
||||
trigger: `.modal .custom_value`,
|
||||
run: `edit ${value}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function selectedCustomAttribute(value) {
|
||||
return [
|
||||
{
|
||||
content: `checking selected custom attribute with value "${value}"`,
|
||||
// trigger: `.modal .custom_value:contains('${value}')`,
|
||||
trigger: `.modal .custom_value`,
|
||||
run: ({ queryAll }) => {
|
||||
const inputs = queryAll(".modal .custom_value");
|
||||
for (const input of inputs) {
|
||||
const actual = input.value?.trim();
|
||||
if (actual === value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
throw new Error(`No custom input found with value "${value}"`);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function numberRadioOptions(number) {
|
||||
return [
|
||||
{
|
||||
trigger: `.attribute-name-cell`,
|
||||
run: () => {
|
||||
const radio_options = document.querySelectorAll(".attribute-name-cell").length;
|
||||
if (radio_options !== number) {
|
||||
throw new Error(`Expected ${number} radio options, got ${radio_options}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isOptionShown(option) {
|
||||
return [
|
||||
{
|
||||
content: `option ${option} is shown`,
|
||||
trigger: `.form-check-label:contains('${option}')`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isUnavailable(option) {
|
||||
return [
|
||||
{
|
||||
content: `option ${option} is unavailable`,
|
||||
trigger: `.modal .attribute span.text-muted:contains('${option}')`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isAddDisabled() {
|
||||
return [
|
||||
{
|
||||
content: "Add button is disabled",
|
||||
trigger: ".modal .btn-primary.disabled",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isAddEnabled() {
|
||||
return [
|
||||
{
|
||||
content: "Add button is enabled",
|
||||
trigger: ".modal .btn-primary:not(.disabled)",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function checkImageVariantVisible() {
|
||||
return [
|
||||
{
|
||||
content: `Check that the image is displayed`,
|
||||
trigger: `.configurator_color.rounded-3`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function checkImageVariantTextVisible(variantName) {
|
||||
return [
|
||||
{
|
||||
content: `Check that the variant is visible`,
|
||||
trigger: `.text-center.mt-2.small span:contains("${variantName}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function checkImagePriceExtraVisible(price) {
|
||||
return [
|
||||
{
|
||||
content: `Check that the extra price is displayed`,
|
||||
trigger: `.price_extra.px-2.py-1.rounded-pill.text-bg-info:contains("${price}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isRadioDisabled(name) {
|
||||
return [
|
||||
{
|
||||
content: `check radio attribute with name ${name}`,
|
||||
trigger: `.modal .attribute-name-cell:contains('${name}') input:disabled`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function priceIs(price) {
|
||||
return [
|
||||
{
|
||||
content: `checking that total price is ${price}`,
|
||||
trigger: `.modal .modal-title:contains('${price}')`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import * as PartnerList from "@point_of_sale/../tests/pos/tours/utils/partner_list_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import { back, selectButton } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
|
||||
export function searchCustomerValueAndClear(val) {
|
||||
return [
|
||||
ProductScreen.clickPartnerButton(),
|
||||
PartnerList.searchCustomerValue(val),
|
||||
selectButton("Discard"),
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
...back(),
|
||||
},
|
||||
].flat();
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,231 @@
|
|||
export function clickNextOrder() {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
content: "go to next screen",
|
||||
trigger: ".receipt-screen .button.next.highlight[name='done']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
content: "go to next screen",
|
||||
trigger: ".receipt-screen .btn-switchpane.validation-button.highlight[name='done']",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickContinueOrder() {
|
||||
return [
|
||||
{
|
||||
content: "go to next screen",
|
||||
trigger: ".receipt-screen .button.next.highlight[name='resume']",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function setEmail(email) {
|
||||
return [
|
||||
{
|
||||
trigger: ".receipt-screen .send-receipt-email-input",
|
||||
run: `edit ${email}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickSend() {
|
||||
return [
|
||||
{
|
||||
run: "click",
|
||||
trigger: `.receipt-screen button i.fa-paper-plane`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickBack() {
|
||||
return [
|
||||
{
|
||||
trigger: ".receipt-screen .button.back",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isShown() {
|
||||
return [
|
||||
{
|
||||
content: "receipt screen is shown",
|
||||
trigger: ".pos .receipt-screen",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptIsThere() {
|
||||
return [
|
||||
{
|
||||
content: "there should be the receipt",
|
||||
trigger: ".receipt-screen .pos-receipt",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function totalAmountContains(value) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"], // not rendered on mobile
|
||||
trigger: `.receipt-screen .o_payment_successful:contains("${value}")`,
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"], // On mobile, at least wait for the receipt screen to show
|
||||
trigger: `.receipt-screen`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptAmountTotalIs(value) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"], // not rendered on mobile
|
||||
trigger: `.receipt-screen .receipt-total:contains("${value}")`,
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"], // On mobile, at least wait for the receipt screen to show
|
||||
trigger: `.receipt-screen`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptRoundingAmountIs(value) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"], // not rendered on mobile
|
||||
trigger: `.receipt-screen .receipt-rounding:contains("${value}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function paymentLineContains(paymentMethodName, amount) {
|
||||
return [
|
||||
{
|
||||
content: `Check if payment line contains ${paymentMethodName} with amount ${amount}`,
|
||||
trigger: `.receipt-screen .paymentlines:contains("${paymentMethodName}"):has(.pos-receipt-right-align:contains("${amount}"))`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptToPayAmountIs(value) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"], // not rendered on mobile
|
||||
trigger: `.receipt-screen .receipt-to-pay:contains("${value}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptToPayAmountIsNotThere() {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"], // not rendered on mobile
|
||||
trigger: ".receipt-screen",
|
||||
run: function () {
|
||||
if (document.querySelector(".receipt-to-pay")) {
|
||||
throw new Error("An amount to pay has been found in receipt.");
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptChangeAmountIs(value) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"], // not rendered on mobile
|
||||
trigger: `.receipt-screen .receipt-change:contains("${value}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptChangeAmountIsNotThere() {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"], // not rendered on mobile
|
||||
trigger: ".receipt-screen",
|
||||
run: function () {
|
||||
if (document.querySelector(".receipt-change")) {
|
||||
throw new Error("An change amount has been found in receipt.");
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
export function emailIsSuccessful() {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .notice .text-success`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function trackingMethodIsLot(lot) {
|
||||
return [
|
||||
{
|
||||
content: `tracking method is Lot`,
|
||||
trigger: `li.lot-number:contains("Lot Number ${lot}")`,
|
||||
run: function () {
|
||||
if (document.querySelectorAll("li.lot-number").length !== 1) {
|
||||
throw new Error(`Expected exactly one 'Lot Number ${lot}' element.`);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function noDiscountAmount() {
|
||||
return [
|
||||
{
|
||||
trigger: `.pos-receipt:not(:contains("Discounts"))`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function shippingDateExists() {
|
||||
return [
|
||||
{
|
||||
content: "Shipping date must be printed",
|
||||
trigger: ".pos-receipt-order-data:contains('Expected delivery:')",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function shippingDateIsToday() {
|
||||
// format the date in US, the language used by the tests
|
||||
const expectedDelivery = new Date().toLocaleString("en-US", luxon.DateTime.DATE_SHORT);
|
||||
|
||||
return [
|
||||
{
|
||||
content: "Shipping date must be today",
|
||||
trigger: `.pos-receipt-order-data:contains('Expected delivery:') > div:contains('${expectedDelivery}')`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function checkOrderlineTaxGroupLabel(label) {
|
||||
return {
|
||||
content: `Verify that the tax group "${label}" appears on the receipt order line.`,
|
||||
trigger: `.pos-receipt .line-details:contains("${label}")`,
|
||||
};
|
||||
}
|
||||
|
||||
export function checkTaxSummaryTaxGroupLabel(label) {
|
||||
return {
|
||||
content: `Verify that the tax group "${label}" appears in the receipt tax summary.`,
|
||||
trigger: `.pos-receipt-taxes:contains('${label}')`,
|
||||
};
|
||||
}
|
||||
|
||||
export function cashierNameExists(name) {
|
||||
return [
|
||||
{
|
||||
content: `Cashier ${name} exists on the receipt`,
|
||||
trigger: `.pos-receipt-contact .cashier:contains(Served by):contains(${name})`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function containsOrderLine(name, quantity, price_unit, line_price) {
|
||||
return [
|
||||
{
|
||||
content: `Order line with name: ${name}, quantity: ${quantity}, price per unit: ${price_unit}, and line price: ${line_price} exists`,
|
||||
trigger: `.pos-receipt .orderline:has(.product-name:contains('${name}')):has(.qty:contains('${quantity}')):has(.product-price:contains('${line_price}')):has(.price-per-unit:contains('${price_unit}'))`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import { isSyncStatusConnected } from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
|
||||
export function nbOrdersIs(nb) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen`,
|
||||
run: () => {
|
||||
const orders = document.querySelectorAll(".ticket-screen .order-row");
|
||||
if (orders.length !== nb) {
|
||||
throw new Error(`Expected ${nb} orders, but found ${orders.length}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
export function clickDiscard() {
|
||||
return {
|
||||
content: "go back",
|
||||
trigger: ".ticket-screen button.discard",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function selectOrder(orderName) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .order-row:contains("${orderName}")`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function selectOrderByPrice(price) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .order-row:contains("${price}")`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `.ticket-screen .order-row.active:contains("${price}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function doubleClickOrder(orderName) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .order-row:contains("${orderName}")`,
|
||||
run: "dblclick",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function loadSelectedOrder() {
|
||||
return [
|
||||
ProductScreen.clickReview(),
|
||||
{
|
||||
trigger: ".ticket-screen .pads .button.validation.load-order-button",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function deleteOrder(orderName) {
|
||||
return [
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
trigger: `.ticket-screen .order-row > div:contains("${orderName}")`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
trigger: `.ticket-screen .order-row:has(div:contains("${orderName}")) .btn-danger`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
trigger: `.ticket-screen .orders .order-row > td:contains("${orderName}") ~ td.text-end button.text-danger`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function selectFilter(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.pos-search-bar .filter`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .filter ul`,
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .filter ul li:contains("${name}")`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function search(field, searchWord) {
|
||||
return [
|
||||
{
|
||||
trigger: ".pos-search-bar input",
|
||||
run: `edit ${
|
||||
field !== "Invoice Number"
|
||||
? searchWord
|
||||
: "TSJ/" + new Date().getFullYear() + "/" + searchWord
|
||||
}`,
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .search ul li:contains("${field}")`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function settleTips() {
|
||||
return [
|
||||
{
|
||||
trigger: ".ticket-screen .controls .settle-tips",
|
||||
run: "click",
|
||||
},
|
||||
isSyncStatusConnected(),
|
||||
];
|
||||
}
|
||||
export function clickControlButton(name) {
|
||||
return [
|
||||
ProductScreen.clickReview(),
|
||||
{
|
||||
trigger: `.ticket-screen ${ProductScreen.controlButtonTrigger(name)}`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function confirmRefund() {
|
||||
return [
|
||||
ProductScreen.clickReview(),
|
||||
{
|
||||
trigger: ".ticket-screen .btn-primary.pay-order-button",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function checkStatus(orderName, status) {
|
||||
return [
|
||||
{
|
||||
isActive: ["desktop"],
|
||||
trigger: `.ticket-screen tbody tr > td:contains("${orderName}") ~ td .badge:contains(${status})`,
|
||||
},
|
||||
{
|
||||
isActive: ["mobile"],
|
||||
trigger: `.ticket-screen .order-row > div:contains("${orderName}") ~ div .badge:contains(${status})`,
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if the nth row contains the given string.
|
||||
* Note that 1st row is the header-row.
|
||||
* @param {boolean | undefined} viewMode true if in mobile view, false if in desktop, undefined if in both views.
|
||||
*/
|
||||
export function nthRowContains(n, string, viewMode) {
|
||||
return [
|
||||
{
|
||||
isActive: [viewMode ? "mobile" : "desktop"],
|
||||
trigger: `.ticket-screen .orders tbody .order-row:nth-child(${n}):contains("${string}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function nthRowIsHighlighted(n) {
|
||||
return [
|
||||
{
|
||||
trigger: ".ticket-screen .order-row.highlight",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function nthRowNotContains(n, string, viewMode) {
|
||||
return [
|
||||
{
|
||||
isActive: [viewMode ? "mobile" : "desktop"],
|
||||
trigger: `.ticket-screen .orders tbody .order-row:nth-child(${n}):not(:contains("${string}"))`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function contains(string) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .orders:contains("${string}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function filterIs(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .pos-search-bar .filter span:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function invoicePrinted() {
|
||||
return [
|
||||
{
|
||||
trigger: ProductScreen.controlButtonTrigger("Reprint Invoice"),
|
||||
},
|
||||
];
|
||||
}
|
||||
export function toRefundTextContains(text) {
|
||||
return inLeftSide({
|
||||
trigger: `.ticket-screen .to-refund-highlight:contains("${text}")`,
|
||||
});
|
||||
}
|
||||
export function toRefundLineContains(product, text) {
|
||||
return inLeftSide({
|
||||
trigger: `.ticket-screen div:has(.product-name:contains("${product}")):has(.to-refund-highlight:contains("${text}"))`,
|
||||
});
|
||||
}
|
||||
export function refundedNoteContains(text) {
|
||||
return inLeftSide({
|
||||
trigger: `.ticket-screen .refund-note:contains("${text}")`,
|
||||
});
|
||||
}
|
||||
export function noLinesToRefund() {
|
||||
return inLeftSide({
|
||||
content: "No lines are marked for to refund or refunding",
|
||||
trigger: ".ticket-screen:not(:has(.to-refund-highlight))",
|
||||
});
|
||||
}
|
||||
export function tipContains(amount) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .tip-cell:contains("${amount}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptTotalIs(amount) {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .pos-receipt-amount:contains("${amount}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function receiptChangeIs(amount) {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .receipt-change:contains("${amount}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function back() {
|
||||
return {
|
||||
isActive: ["mobile"],
|
||||
trigger: ".back-button",
|
||||
run: "click",
|
||||
};
|
||||
}
|
||||
export function checkCameraIsOpen() {
|
||||
return {
|
||||
content: "Verify that the camera view is visible in the left pane.",
|
||||
trigger: ".ticket-screen .leftpane .o_crop_container",
|
||||
};
|
||||
}
|
||||
|
||||
export function noOrderIsThere() {
|
||||
return {
|
||||
content: "No orders should be visible on the Ticket Screen",
|
||||
trigger: ".ticket-screen:not(:has(.order-row))",
|
||||
};
|
||||
}
|
||||
|
||||
export function isShown() {
|
||||
return [
|
||||
{
|
||||
content: "ticket screen is shown",
|
||||
trigger: ".pos .ticket-screen",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.BarcodeScanning', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
const Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
|
||||
// Add a product with its barcode
|
||||
ProductScreen.do.scan_barcode("0123456789");
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand');
|
||||
ProductScreen.do.scan_barcode("0123456789");
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand', 2);
|
||||
|
||||
// Test "Prices product" EAN-13 `23.....{NNNDD}` barcode pattern
|
||||
ProductScreen.do.scan_ean13_barcode("2305000000004");
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', 1, "0.00");
|
||||
ProductScreen.do.scan_ean13_barcode("2305000123451");
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', 1, "123.45");
|
||||
|
||||
// Test "Weighted product" EAN-13 `21.....{NNDDD}` barcode pattern
|
||||
ProductScreen.do.scan_ean13_barcode("2100005000000");
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', 0, "0.00");
|
||||
ProductScreen.do.scan_ean13_barcode("2100005080002");
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', 8);
|
||||
|
||||
|
||||
Tour.register('BarcodeScanningTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
// Add the Product 1 with GS1 barcode
|
||||
ProductScreen.do.scan_barcode("0108431673020125100000001");
|
||||
ProductScreen.check.selectedOrderlineHas('Product 1');
|
||||
ProductScreen.do.scan_barcode("0108431673020125100000001");
|
||||
ProductScreen.check.selectedOrderlineHas('Product 1', 2);
|
||||
|
||||
// Add the Product 2 with normal barcode
|
||||
ProductScreen.do.scan_barcode("08431673020126");
|
||||
ProductScreen.check.selectedOrderlineHas('Product 2');
|
||||
ProductScreen.do.scan_barcode("08431673020126");
|
||||
ProductScreen.check.selectedOrderlineHas('Product 2', 2);
|
||||
|
||||
// Add the Product 3 with normal barcode
|
||||
ProductScreen.do.scan_barcode("3760171283370");
|
||||
ProductScreen.check.selectedOrderlineHas('Product 3');
|
||||
ProductScreen.do.scan_barcode("3760171283370");
|
||||
ProductScreen.check.selectedOrderlineHas('Product 3', 2);
|
||||
|
||||
Tour.register('GS1BarcodeScanningTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.Chrome', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
// Order 1 is at Product Screen
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '2', '2.0');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0001', 'Ongoing');
|
||||
|
||||
// Order 2 is at Payment Screen
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Monitor Stand', '3', '4', '12.0');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0002', 'Payment');
|
||||
|
||||
// Order 3 is at Receipt Screen
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5', '6', '30.0');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(true);
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0003', 'Receipt');
|
||||
|
||||
// Select order 1, should be at Product Screen
|
||||
TicketScreen.do.selectOrder('-0001');
|
||||
ProductScreen.check.productIsDisplayed('Desk Pad');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Pad', '1.0', '2.0');
|
||||
|
||||
// Select order 2, should be at Payment Screen
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectOrder('-0002');
|
||||
PaymentScreen.check.emptyPaymentlines('12.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(false);
|
||||
|
||||
// Select order 3, should be at Receipt Screen
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
ReceiptScreen.check.totalAmountContains('30.0');
|
||||
|
||||
// Pay order 1, with change
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectOrder('-0001');
|
||||
ProductScreen.check.isShown();
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('2 0');
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('18.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(true);
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.totalAmountContains('2.0');
|
||||
|
||||
// Order 1 now should have Receipt status
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.checkStatus('-0001', 'Receipt');
|
||||
|
||||
// Select order 3, should still be at Receipt Screen
|
||||
// and the total amount doesn't change.
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
ReceiptScreen.check.totalAmountContains('30.0');
|
||||
|
||||
// click next screen on order 3
|
||||
// then delete the new empty order
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.deleteOrder('-0004');
|
||||
TicketScreen.do.deleteOrder('-0001');
|
||||
|
||||
// After deleting order 1 above, order 2 became
|
||||
// the 2nd-row order and it has payment status
|
||||
TicketScreen.check.nthRowContains(2, 'Payment')
|
||||
TicketScreen.do.deleteOrder('-0002');
|
||||
Chrome.do.confirmPopup();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
|
||||
// Invoice an order
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5', '6');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Nicole Ford');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.clickInvoiceButton();
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
|
||||
Tour.register('ChromeTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.PaymentScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');
|
||||
const { ErrorPopup } = require('point_of_sale.tour.ErrorPopupTourMethods');
|
||||
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 { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.exec.addOrderline('Letter Tray', '10');
|
||||
ProductScreen.check.selectedOrderlineHas('Letter Tray', '10.0');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.check.emptyPaymentlines('52.8');
|
||||
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('1 1');
|
||||
PaymentScreen.check.selectedPaymentlineHas('Cash', '11.00');
|
||||
PaymentScreen.check.remainingIs('41.8');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(false);
|
||||
// remove the selected paymentline with multiple backspace presses
|
||||
PaymentScreen.do.pressNumpad('Backspace Backspace');
|
||||
PaymentScreen.check.selectedPaymentlineHas('Cash', '0.00');
|
||||
PaymentScreen.do.pressNumpad('Backspace');
|
||||
PaymentScreen.check.emptyPaymentlines('52.8');
|
||||
|
||||
// Pay with bank, the selected line should have full amount
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(true);
|
||||
// remove the line using the delete button
|
||||
PaymentScreen.do.clickPaymentlineDelButton('Bank', '52.8');
|
||||
|
||||
// Use +10 and +50 to increment the amount of the paymentline
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('+10');
|
||||
PaymentScreen.check.remainingIs('42.8');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(false);
|
||||
PaymentScreen.do.pressNumpad('+50');
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('7.2');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(true);
|
||||
PaymentScreen.do.clickPaymentlineDelButton('Cash', '60.0');
|
||||
|
||||
// Multiple paymentlines
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('1');
|
||||
PaymentScreen.check.remainingIs('51.8');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(false);
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('5');
|
||||
PaymentScreen.check.remainingIs('46.8');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(false);
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.pressNumpad('2 0');
|
||||
PaymentScreen.check.remainingIs('26.8');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(false);
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(true);
|
||||
|
||||
Tour.register('PaymentScreenTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Letter Tray', '1', '10');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.pressNumpad('1 0 0 0');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Tour.register('PaymentScreenTour2', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Product Test', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('2.00');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
|
||||
ProductScreen.exec.addOrderline('Product Test', '-1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('-2.00');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Tour.register('PaymentScreenRoundingUp', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Product Test', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('1.95');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
|
||||
ProductScreen.exec.addOrderline('Product Test', '-1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('-1.95');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Tour.register('PaymentScreenRoundingDown', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Product Test 1.2', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('1.00');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
|
||||
ProductScreen.exec.addOrderline('Product Test 1.25', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('1.5');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
|
||||
ProductScreen.exec.addOrderline('Product Test 1.4', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('1.5');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.clickNewTicket();
|
||||
|
||||
ProductScreen.exec.addOrderline('Product Test 1.2', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('1.00');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('2');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('1.0');
|
||||
|
||||
Tour.register('PaymentScreenRoundingHalfUp', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Product Test 40', '1');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Nicole Ford');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('40.00');
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.pressNumpad('3 8');
|
||||
PaymentScreen.check.remainingIs('2.0');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
PaymentScreen.do.clickInvoiceButton();
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Product Test 41', '1');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Nicole Ford');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('41.00');
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.pressNumpad('3 8');
|
||||
PaymentScreen.check.remainingIs('3.0');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('0.0');
|
||||
|
||||
PaymentScreen.do.clickInvoiceButton();
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
|
||||
Tour.register('PaymentScreenRoundingHalfUpCashAndBank', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Product Test', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.check.totalIs('1.95');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('5');
|
||||
|
||||
PaymentScreen.check.remainingIs('0.0');
|
||||
PaymentScreen.check.changeIs('3.05');
|
||||
PaymentScreen.check.totalDueIs('1.95');
|
||||
|
||||
Tour.register('PaymentScreenTotalDueWithOverPayment', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Magnetic Board', '1');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
// Check the popup error is shown when selecting another payment method
|
||||
PaymentScreen.check.totalIs('1.90');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('1 .');
|
||||
PaymentScreen.check.selectedPaymentlineHas('Cash', '1.00');
|
||||
PaymentScreen.do.pressNumpad('2 4');
|
||||
PaymentScreen.check.selectedPaymentlineHas('Cash', '1.24');
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
ErrorPopup.check.isShown();
|
||||
ErrorPopup.check.messageBodyContains( // Verify the value displayed are as expected
|
||||
'The rounding precision is 0.10 so you should set 1.20 or 1.30 as payment amount instead of 1.24.'
|
||||
);
|
||||
|
||||
Tour.register('CashRoundingPayment', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.exec.addOrderline('Letter Tray', '5');
|
||||
ProductScreen.check.selectedOrderlineHas('Letter Tray', '5.0');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Nicole Ford');
|
||||
ProductScreen.do.clickPayButton();
|
||||
|
||||
PaymentScreen.do.clickPaymentMethod('New Cash');
|
||||
PaymentScreen.do.pressNumpad('5 5');
|
||||
PaymentScreen.check.selectedPaymentlineHas('New Cash', '55');
|
||||
PaymentScreen.do.clickInvoiceButton();
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
|
||||
Tour.register('MultipleCashPaymentMethod', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ProductConfigurator', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { ProductConfigurator } = require('point_of_sale.tour.ProductConfiguratorTourMethods');
|
||||
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();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
// Go by default to home category
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Click on Configurable Chair product
|
||||
ProductScreen.do.clickDisplayedProduct('Configurable Chair');
|
||||
ProductConfigurator.check.isShown();
|
||||
|
||||
// Cancel configuration, not product should be in order
|
||||
ProductConfigurator.do.cancelAttributes();
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
|
||||
// Click on Configurable Chair product
|
||||
ProductScreen.do.clickDisplayedProduct('Configurable Chair');
|
||||
ProductConfigurator.check.isShown();
|
||||
|
||||
// Pick Color
|
||||
ProductConfigurator.do.pickColor('Red');
|
||||
|
||||
// Pick Radio
|
||||
ProductConfigurator.do.pickSelect('Metal');
|
||||
|
||||
// Pick Select
|
||||
ProductConfigurator.do.pickRadio('Other');
|
||||
|
||||
// Fill in custom attribute
|
||||
ProductConfigurator.do.fillCustomAttribute('Custom Fabric');
|
||||
|
||||
// Confirm configuration
|
||||
ProductConfigurator.do.confirmAttributes();
|
||||
|
||||
// Check that the product has been added to the order with correct attributes and price
|
||||
ProductScreen.check.selectedOrderlineHas('Configurable Chair (Red, Metal, Other: Custom Fabric)', '1.0', '11.0');
|
||||
|
||||
// Orderlines with the same attributes should be merged
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Configurable Chair');
|
||||
ProductConfigurator.do.pickColor('Red');
|
||||
ProductConfigurator.do.pickSelect('Metal');
|
||||
ProductConfigurator.do.pickRadio('Other');
|
||||
ProductConfigurator.do.fillCustomAttribute('Custom Fabric');
|
||||
ProductConfigurator.do.confirmAttributes();
|
||||
ProductScreen.check.selectedOrderlineHas('Configurable Chair (Red, Metal, Other: Custom Fabric)', '2.0', '22.0');
|
||||
|
||||
// Orderlines with different attributes shouldn't be merged
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Configurable Chair');
|
||||
ProductConfigurator.do.pickColor('Blue');
|
||||
ProductConfigurator.do.pickSelect('Metal');
|
||||
ProductConfigurator.do.pickRadio('Leather');
|
||||
ProductConfigurator.do.confirmAttributes();
|
||||
ProductScreen.check.selectedOrderlineHas('Configurable Chair (Blue, Metal, Leather)', '1.0', '10.0');
|
||||
|
||||
Tour.register('ProductConfiguratorTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Configurable Chair');
|
||||
ProductConfigurator.check.isShown();
|
||||
// Option Other is active, Leather is not -> only 1 option available
|
||||
ProductConfigurator.check.numberRadioOptions(1);
|
||||
|
||||
Tour.register('InactiveAttributeValueTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
|
@ -1,258 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ProductScreen', 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 { TextAreaPopup } = require('point_of_sale.tour.TextAreaPopupTourMethods');
|
||||
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();
|
||||
|
||||
// Go by default to home category
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Clicking product multiple times should increment quantity
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.0', '5.10');
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '2.0', '10.20');
|
||||
|
||||
// Clicking product should add new orderline and select the orderline
|
||||
// If orderline exists, increment the quantity
|
||||
ProductScreen.do.clickDisplayedProduct('Letter Tray');
|
||||
ProductScreen.check.selectedOrderlineHas('Letter Tray', '1.0', '5.28');
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '3.0', '15.30');
|
||||
|
||||
// Check effects of clicking numpad buttons
|
||||
ProductScreen.do.clickOrderline('Letter Tray', '1');
|
||||
ProductScreen.check.selectedOrderlineHas('Letter Tray', '1.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Letter Tray', '0.0', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '3', '15.30');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '0.0', '0.0');
|
||||
ProductScreen.do.pressNumpad('1');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.0', '5.1');
|
||||
ProductScreen.do.pressNumpad('2');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '12.0', '61.2');
|
||||
ProductScreen.do.pressNumpad('3');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.0', '627.3');
|
||||
ProductScreen.do.pressNumpad('. 5');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '629.85');
|
||||
ProductScreen.do.pressNumpad('Price');
|
||||
ProductScreen.do.pressNumpad('1');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '123.5');
|
||||
ProductScreen.do.pressNumpad('1 .');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '1,358.5');
|
||||
ProductScreen.do.pressNumpad('Disc');
|
||||
ProductScreen.do.pressNumpad('5 .');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '1,290.58');
|
||||
ProductScreen.do.pressNumpad('Qty');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
|
||||
// Check different subcategories
|
||||
ProductScreen.do.clickSubcategory('Desks');
|
||||
ProductScreen.check.productIsDisplayed('Desk Pad');
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickSubcategory('Miscellaneous');
|
||||
ProductScreen.check.productIsDisplayed('Whiteboard Pen');
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickSubcategory('Chairs');
|
||||
ProductScreen.check.productIsDisplayed('Letter Tray');
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Add two orderlines and update quantity
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.do.clickDisplayedProduct('Wall Shelf Unit');
|
||||
ProductScreen.do.clickOrderline('Whiteboard Pen', '1.0');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.0');
|
||||
ProductScreen.do.pressNumpad('2');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.0');
|
||||
ProductScreen.do.clickOrderline('Wall Shelf Unit', '1.0');
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '1.0');
|
||||
ProductScreen.do.pressNumpad('2');
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '2.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
|
||||
// Add multiple orderlines then delete each of them until empty
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.do.clickDisplayedProduct('Wall Shelf Unit');
|
||||
ProductScreen.do.clickDisplayedProduct('Small Shelf');
|
||||
ProductScreen.do.clickDisplayedProduct('Magnetic Board');
|
||||
ProductScreen.do.clickDisplayedProduct('Monitor Stand');
|
||||
ProductScreen.do.clickOrderline('Whiteboard Pen', '1.0');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');
|
||||
ProductScreen.do.clickOrderline('Wall Shelf Unit', '1.0');
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '1.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');
|
||||
ProductScreen.do.clickOrderline('Small Shelf', '1.0');
|
||||
ProductScreen.check.selectedOrderlineHas('Small Shelf', '1.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Small Shelf', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');
|
||||
ProductScreen.do.clickOrderline('Magnetic Board', '1.0');
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '1.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand', '0.0');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
|
||||
// Test OrderlineCustomerNoteButton
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.do.clickOrderlineCustomerNoteButton();
|
||||
TextAreaPopup.check.isShown();
|
||||
TextAreaPopup.do.inputText('Test customer note');
|
||||
TextAreaPopup.do.clickConfirm();
|
||||
ProductScreen.check.orderlineHasCustomerNote('Desk Organizer', '1', 'Test customer note');
|
||||
|
||||
|
||||
Tour.register('ProductScreenTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product');
|
||||
ProductScreen.check.totalAmountIs('100.00');
|
||||
ProductScreen.do.changeFiscalPosition('No Tax');
|
||||
ProductScreen.check.noDiscountApplied("100.00");
|
||||
ProductScreen.check.totalAmountIs('86.96');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.remainingIs('0.00');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
ReceiptScreen.check.noOrderlineContainsDiscount();
|
||||
|
||||
Tour.register('FiscalPositionNoTax', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
||||
odoo.define('point_of_sale.tour.FixedPriceNegativeQty', 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 { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Zero Amount Product');
|
||||
ProductScreen.check.selectedOrderlineHas('Zero Amount Product', '1.0', '1.0');
|
||||
ProductScreen.do.pressNumpad('+/- 1');
|
||||
ProductScreen.check.selectedOrderlineHas('Zero Amount Product', '-1.0', '-1.0');
|
||||
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.remainingIs('0.00');
|
||||
PaymentScreen.do.clickValidate();
|
||||
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
|
||||
Tour.register('FixedTaxNegativeQty', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
||||
odoo.define('point_of_sale.tour.OpenCloseCashCount', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.enterOpeningAmount('90');
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.check.checkSecondCashClosingDetailsLineAmount('10.00', '-');
|
||||
|
||||
Tour.register('CashClosingDetails', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
||||
odoo.define('point_of_sale.tour.RoundGloballyTax', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product');
|
||||
ProductScreen.check.totalAmountIs('115.00');
|
||||
|
||||
Tour.register('RoundGloballyAmoundTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
||||
odoo.define('point_of_sale.tour.ShowTaxExcludedTour', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product');
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product', '1.0', '100.0');
|
||||
ProductScreen.check.totalAmountIs('110.0');
|
||||
|
||||
Tour.register('ShowTaxExcludedTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
||||
odoo.define('point_of_sale.tour.limitedProductPricelistLoading', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.scan_barcode("0100100");
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product 1', '1.0', '80.0');
|
||||
|
||||
ProductScreen.do.scan_barcode("0100200");
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product 2', '1.0', '100.0');
|
||||
|
||||
ProductScreen.do.scan_barcode("0100300");
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product 3', '1.0', '50.0');
|
||||
|
||||
Tour.register('limitedProductPricelistLoading', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ReceiptScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
const Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
// press close button in receipt screen
|
||||
ProductScreen.exec.addOrderline('Letter Tray', '10', '5');
|
||||
ProductScreen.check.selectedOrderlineHas('Letter Tray', '10');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.validateButtonIsHighlighted(true);
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
// letter tray has 10% tax (search SRC)
|
||||
ReceiptScreen.check.totalAmountContains('55.0');
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
|
||||
// send email in receipt screen
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '6', '5', '30.0');
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '6', '6', '36.0');
|
||||
ProductScreen.exec.addOrderline('Monitor Stand', '6', '1', '6.0');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.pressNumpad('7 0');
|
||||
PaymentScreen.check.remainingIs('2.0');
|
||||
PaymentScreen.do.pressNumpad('0');
|
||||
PaymentScreen.check.remainingIs('0.00');
|
||||
PaymentScreen.check.changeIs('628.0');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
ReceiptScreen.check.totalAmountContains('72.0');
|
||||
ReceiptScreen.do.setEmail('test@receiptscreen.com');
|
||||
ReceiptScreen.do.clickSend();
|
||||
ReceiptScreen.check.emailIsSuccessful();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
|
||||
// order with tip
|
||||
// check if tip amount is displayed
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '6', '5');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickTipButton();
|
||||
NumberPopup.do.pressNumpad('1');
|
||||
NumberPopup.check.inputShownIs('1');
|
||||
NumberPopup.do.clickConfirm();
|
||||
PaymentScreen.check.emptyPaymentlines('31.0');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
ReceiptScreen.check.totalAmountContains('$ 30.00 + $ 1.00 tip');
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
|
||||
// Test customer note in receipt
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '5');
|
||||
ProductScreen.exec.addCustomerNote('Test customer note')
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.customerNoteIsThere('Test customer note');
|
||||
|
||||
Tour.register('ReceiptScreenTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Test Product', '1');
|
||||
ProductScreen.do.clickPricelistButton();
|
||||
ProductScreen.do.selectPriceList('special_pricelist');
|
||||
ProductScreen.check.discountOriginalPriceIs('7.0');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.discountAmountIs('0.7');
|
||||
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
ProductScreen.exec.addOrderline('Test Product', '1');
|
||||
ProductScreen.do.pressNumpad('Price');
|
||||
ProductScreen.do.pressNumpad('9');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.noDiscountAmount();
|
||||
|
||||
Tour.register('ReceiptScreenDiscountWithPricelistTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.do.enterLotNumber('123456789');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.trackingMethodIsLot();
|
||||
|
||||
Tour.register('ReceiptTrackingMethodTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.TicketScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { PartnerListScreen } = require('point_of_sale.tour.PartnerListScreenTourMethods');
|
||||
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
|
||||
const { ErrorPopup } = require('point_of_sale.tour.ErrorPopupTourMethods');
|
||||
const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '2');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Nicole Ford');
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.nthRowContains(2, 'Nicole Ford');
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '1', '3');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Brandon Freeman');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.nthRowContains(3, 'Brandon Freeman');
|
||||
TicketScreen.do.clickNewTicket();
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '2', '4');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.check.nthRowContains(4, 'Receipt');
|
||||
TicketScreen.do.selectFilter('Receipt');
|
||||
TicketScreen.check.nthRowContains(2, 'Receipt');
|
||||
TicketScreen.do.selectFilter('Payment');
|
||||
TicketScreen.check.nthRowContains(2, 'Payment');
|
||||
TicketScreen.do.selectFilter('Ongoing');
|
||||
TicketScreen.check.nthRowContains(2, 'Ongoing');
|
||||
TicketScreen.do.selectFilter('All active orders');
|
||||
TicketScreen.check.nthRowContains(4, 'Receipt');
|
||||
TicketScreen.do.search('Customer', 'Nicole');
|
||||
TicketScreen.check.nthRowContains(2, 'Nicole');
|
||||
TicketScreen.do.search('Customer', 'Brandon');
|
||||
TicketScreen.check.nthRowContains(2, 'Brandon');
|
||||
TicketScreen.do.search('Receipt Number', '-0003');
|
||||
TicketScreen.check.nthRowContains(2, 'Receipt');
|
||||
// Close the TicketScreen to see the current order which is in ReceiptScreen.
|
||||
// This is just to remove the search string in the search bar.
|
||||
TicketScreen.do.clickDiscard();
|
||||
ReceiptScreen.check.isShown();
|
||||
// Open again the TicketScreen to check the Paid filter.
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectFilter('Paid');
|
||||
TicketScreen.check.nthRowContains(2, '-0003');
|
||||
// Pay the order that was in PaymentScreen.
|
||||
TicketScreen.do.selectFilter('Payment');
|
||||
TicketScreen.do.selectOrder('-0002');
|
||||
PaymentScreen.do.clickPaymentMethod('Cash');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
ProductScreen.check.isShown();
|
||||
// Check that the Paid filter will show the 2 synced orders.
|
||||
Chrome.do.clickTicketButton();
|
||||
TicketScreen.do.selectFilter('Paid');
|
||||
TicketScreen.check.nthRowContains(2, 'Brandon Freeman');
|
||||
TicketScreen.check.nthRowContains(3, '-0003');
|
||||
// Invoice order
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
TicketScreen.check.orderWidgetIsNotEmpty();
|
||||
TicketScreen.do.clickControlButton('Invoice');
|
||||
Chrome.do.confirmPopup();
|
||||
PartnerListScreen.check.isShown();
|
||||
PartnerListScreen.do.clickPartner('Colleen Diaz');
|
||||
TicketScreen.check.partnerIs('Colleen Diaz');
|
||||
// Reprint receipt
|
||||
TicketScreen.do.clickControlButton('Print Receipt');
|
||||
ReceiptScreen.check.isShown();
|
||||
ReceiptScreen.do.clickBack();
|
||||
// When going back, the ticket screen should be in its previous state.
|
||||
TicketScreen.check.filterIs('Paid');
|
||||
|
||||
// Test refund //
|
||||
TicketScreen.do.clickDiscard();
|
||||
ProductScreen.check.isShown();
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
ProductScreen.do.clickRefund();
|
||||
// Filter should be automatically 'Paid'.
|
||||
TicketScreen.check.filterIs('Paid');
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
TicketScreen.check.partnerIs('Colleen Diaz');
|
||||
TicketScreen.do.clickOrderline('Desk Pad');
|
||||
TicketScreen.do.pressNumpad('3');
|
||||
// Error should show because 2 is more than the number
|
||||
// that can be refunded.
|
||||
ErrorPopup.do.clickConfirm();
|
||||
TicketScreen.do.clickDiscard();
|
||||
ProductScreen.check.isShown();
|
||||
ProductScreen.check.orderIsEmpty();
|
||||
ProductScreen.do.clickRefund();
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
TicketScreen.do.clickOrderline('Desk Pad');
|
||||
TicketScreen.do.pressNumpad('1');
|
||||
TicketScreen.check.toRefundTextContains('To Refund: 1.00');
|
||||
TicketScreen.do.confirmRefund();
|
||||
ProductScreen.check.isShown();
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Pad', '-1.00');
|
||||
// Try changing the refund line to positive number.
|
||||
// Error popup should show.
|
||||
ProductScreen.do.pressNumpad('2');
|
||||
ErrorPopup.do.clickConfirm();
|
||||
// Change the refund line quantity to -3 -- not allowed
|
||||
// so error popup.
|
||||
ProductScreen.do.pressNumpad('+/- 3');
|
||||
ErrorPopup.do.clickConfirm();
|
||||
// Change the refund line quantity to -2 -- allowed.
|
||||
ProductScreen.do.pressNumpad('+/- 2');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Pad', '-2.00');
|
||||
// Check if the amount being refunded changed to 2.
|
||||
ProductScreen.do.clickRefund();
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
TicketScreen.check.toRefundTextContains('Refunding 2.00');
|
||||
TicketScreen.do.clickDiscard();
|
||||
// Pay the refund order.
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
// Check refunded quantity.
|
||||
ProductScreen.do.clickRefund();
|
||||
TicketScreen.do.selectOrder('-0003');
|
||||
TicketScreen.check.refundedNoteContains('2.00 Refunded');
|
||||
|
||||
Tour.register('TicketScreenTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Product Test');
|
||||
ProductScreen.check.totalAmountIs('100.00');
|
||||
ProductScreen.do.changeFiscalPosition('No Tax');
|
||||
ProductScreen.check.totalAmountIs('86.96');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.remainingIs('0.00');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
ProductScreen.do.clickRefund();
|
||||
TicketScreen.do.selectOrder('-0001');
|
||||
TicketScreen.do.clickOrderline('Product Test');
|
||||
TicketScreen.do.pressNumpad('1');
|
||||
TicketScreen.check.toRefundTextContains('To Refund: 1.00');
|
||||
TicketScreen.do.confirmRefund();
|
||||
ProductScreen.check.isShown();
|
||||
ProductScreen.check.totalAmountIs('-86.96');
|
||||
|
||||
Tour.register('FiscalPositionNoTaxRefund', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.do.enterLotNumber('123456789');
|
||||
ProductScreen.check.selectedOrderlineHas('Product A', '1.00');
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
ReceiptScreen.do.clickNextOrder();
|
||||
ProductScreen.do.clickRefund();
|
||||
TicketScreen.do.selectOrder('-0001');
|
||||
TicketScreen.do.clickOrderline('Product A');
|
||||
TicketScreen.do.pressNumpad('1');
|
||||
TicketScreen.check.toRefundTextContains('To Refund: 1.00');
|
||||
TicketScreen.do.confirmRefund();
|
||||
ProductScreen.check.isShown();
|
||||
ProductScreen.do.clickLotIcon();
|
||||
ProductScreen.check.checkFirstLotNumber('123456789');
|
||||
|
||||
Tour.register('LotRefundTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct("Test Product");
|
||||
ProductScreen.check.checkTaxAmount("9.09");
|
||||
ProductScreen.check.totalAmountIs("100.00");
|
||||
ProductScreen.do.changeFiscalPosition("test fp");
|
||||
ProductScreen.check.totalAmountIs("100.00");
|
||||
ProductScreen.check.checkTaxAmount("4.76");
|
||||
|
||||
Tour.register("FiscalPositionTwoTaxIncluded", { test: true, url: "/pos/ui" }, getSteps());
|
||||
});
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ChromeTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
confirmPopup() {
|
||||
return [
|
||||
{
|
||||
content: 'confirm popup',
|
||||
trigger: '.popups .modal-dialog .button.confirm',
|
||||
},
|
||||
];
|
||||
}
|
||||
clickTicketButton() {
|
||||
return [
|
||||
{
|
||||
trigger: '.pos-topheader .ticket-button',
|
||||
},
|
||||
{
|
||||
trigger: '.subwindow .ticket-screen',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('Chrome', Do);
|
||||
});
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ErrorPopupTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
clickConfirm() {
|
||||
return [
|
||||
{
|
||||
content: 'click confirm button',
|
||||
trigger: '.popup-error .footer .cancel',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'error popup is shown',
|
||||
trigger: '.modal-dialog .popup-error',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
messageBodyContains(text) {
|
||||
return [
|
||||
{
|
||||
content: `check '${text}' is in the body of the popup`,
|
||||
trigger: `.modal-dialog .popup-error .body:contains(${text})`,
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('ErrorPopup', Do, Check);
|
||||
});
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.NumberPopupTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
/**
|
||||
* Note: Maximum of 2 characters because NumberBuffer only allows 2 consecutive
|
||||
* fast inputs. Fast inputs is the case in tours.
|
||||
*
|
||||
* @param {String} keys space-separated input keys
|
||||
*/
|
||||
pressNumpad(keys) {
|
||||
const numberChars = '0 1 2 3 4 5 6 7 8 9 C'.split(' ');
|
||||
const modeButtons = '+1 +10 +2 +20 +5 +50'.split(' ');
|
||||
const decimalSeparators = ', .'.split(' ');
|
||||
function generateStep(key) {
|
||||
let trigger;
|
||||
if (numberChars.includes(key)) {
|
||||
trigger = `.popup-numpad .number-char:contains("${key}")`;
|
||||
} else if (modeButtons.includes(key)) {
|
||||
trigger = `.popup-numpad .mode-button:contains("${key}")`;
|
||||
} else if (key === 'Backspace') {
|
||||
trigger = `.popup-numpad .numpad-backspace`;
|
||||
} else if (decimalSeparators.includes(key)) {
|
||||
trigger = `.popup-numpad .number-char.dot`;
|
||||
}
|
||||
return {
|
||||
content: `'${key}' pressed in numpad`,
|
||||
trigger,
|
||||
};
|
||||
}
|
||||
return keys.split(' ').map(generateStep);
|
||||
}
|
||||
clickConfirm() {
|
||||
return [
|
||||
{
|
||||
content: 'click confirm button',
|
||||
trigger: '.popup-number .footer .confirm',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'number popup is shown',
|
||||
trigger: '.modal-dialog .popup-number',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
inputShownIs(val) {
|
||||
return [
|
||||
{
|
||||
content: 'number input element check',
|
||||
trigger: '.modal-dialog .popup-number .popup-input',
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
content: `input shown is '${val}'`,
|
||||
trigger: `.modal-dialog .popup-number .popup-input:contains("${val}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('NumberPopup', Do, Check);
|
||||
});
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.PartnerListScreenTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
clickPartner(name) {
|
||||
return [
|
||||
{
|
||||
content: `click partner '${name}' from partner list screen`,
|
||||
trigger: `.partnerlist-screen .partner-list-contents .partner-line td:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
clickPartnerDetailsButton(name) {
|
||||
return [
|
||||
{
|
||||
content: `click partner details '${name}' from partner list screen`,
|
||||
trigger: `.partner-line:contains('${name}') .edit-partner-button`,
|
||||
}
|
||||
]
|
||||
}
|
||||
clickBack() {
|
||||
return [
|
||||
{
|
||||
trigger: ".partnerlist-screen .button.back",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'partner list screen is shown',
|
||||
trigger: '.pos-content .partnerlist-screen',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {}
|
||||
|
||||
return createTourMethods('PartnerListScreen', Do, Check, Execute);
|
||||
});
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.PaymentScreenTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
clickPaymentMethod(name) {
|
||||
return [
|
||||
{
|
||||
content: `click '${name}' payment method`,
|
||||
trigger: `.paymentmethods .button.paymentmethod:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the paymentline having the given payment method name and amount.
|
||||
* @param {String} name payment method
|
||||
* @param {String} amount
|
||||
*/
|
||||
clickPaymentlineDelButton(name, amount) {
|
||||
return [
|
||||
{
|
||||
content: `delete ${name} paymentline with ${amount} amount`,
|
||||
trigger: `.paymentlines .paymentline .payment-name:contains("${name}") ~ .delete-button`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickEmailButton() {
|
||||
return [
|
||||
{
|
||||
content: `click email button`,
|
||||
trigger: `.payment-buttons .js_email`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickInvoiceButton() {
|
||||
return [{ content: 'click invoice button', trigger: '.payment-buttons .js_invoice' }];
|
||||
}
|
||||
|
||||
clickValidate() {
|
||||
return [
|
||||
{
|
||||
content: 'validate payment',
|
||||
trigger: `.payment-screen .button.next.highlight`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Press the numpad in sequence based on the given space-separated keys.
|
||||
* Note: Maximum of 2 characters because NumberBuffer only allows 2 consecutive
|
||||
* fast inputs. Fast inputs is the case in tours.
|
||||
*
|
||||
* @param {String} keys space-separated numpad keys
|
||||
*/
|
||||
pressNumpad(keys) {
|
||||
const numberChars = '. +/- 0 1 2 3 4 5 6 7 8 9'.split(' ');
|
||||
const modeButtons = '+10 +20 +50'.split(' ');
|
||||
function generateStep(key) {
|
||||
let trigger;
|
||||
if (numberChars.includes(key)) {
|
||||
trigger = `.payment-numpad .number-char:contains("${key}")`;
|
||||
} else if (modeButtons.includes(key)) {
|
||||
trigger = `.payment-numpad .mode-button:contains("${key}")`;
|
||||
} else if (key === 'Backspace') {
|
||||
trigger = `.payment-numpad .number-char img[alt="Backspace"]`;
|
||||
}
|
||||
return {
|
||||
content: `'${key}' pressed in payment numpad`,
|
||||
trigger,
|
||||
};
|
||||
}
|
||||
return keys.split(' ').map(generateStep);
|
||||
}
|
||||
|
||||
clickBack() {
|
||||
return [
|
||||
{
|
||||
content: 'click back button',
|
||||
trigger: '.payment-screen .button.back',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickTipButton() {
|
||||
return [
|
||||
{
|
||||
trigger: '.payment-screen .button.js_tip',
|
||||
},
|
||||
]
|
||||
}
|
||||
clickShipLaterButton() {
|
||||
return [
|
||||
{
|
||||
content: 'click ship later button',
|
||||
trigger: '.button:contains("Ship Later")',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'payment screen is shown',
|
||||
trigger: '.pos .payment-screen',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if change is the provided amount.
|
||||
* @param {String} amount
|
||||
*/
|
||||
changeIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `change is ${amount}`,
|
||||
trigger: `.payment-status-change .amount:contains("${amount}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the remaining is the provided amount.
|
||||
* @param {String} amount
|
||||
*/
|
||||
remainingIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `remaining amount is ${amount}`,
|
||||
trigger: `.payment-status-remaining .amount:contains("${amount}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if validate button is highlighted.
|
||||
* @param {Boolean} isHighlighted
|
||||
*/
|
||||
validateButtonIsHighlighted(isHighlighted = true) {
|
||||
return [
|
||||
{
|
||||
content: `validate button is ${
|
||||
isHighlighted ? 'highlighted' : 'not highligted'
|
||||
}`,
|
||||
trigger: isHighlighted
|
||||
? `.payment-screen .button.next.highlight`
|
||||
: `.payment-screen .button.next:not(:has(.highlight))`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the paymentlines are empty. Also provide the amount to pay.
|
||||
* @param {String} amountToPay
|
||||
*/
|
||||
emptyPaymentlines(amountToPay) {
|
||||
return [
|
||||
{
|
||||
content: `there are no paymentlines`,
|
||||
trigger: `.paymentlines-empty`,
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
content: `amount to pay is '${amountToPay}'`,
|
||||
trigger: `.paymentlines-empty .total:contains("${amountToPay}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the selected paymentline has the given payment method and amount.
|
||||
* @param {String} paymentMethodName
|
||||
* @param {String} amount
|
||||
*/
|
||||
selectedPaymentlineHas(paymentMethodName, amount) {
|
||||
return [
|
||||
{
|
||||
content: `line paid via '${paymentMethodName}' is selected`,
|
||||
trigger: `.paymentlines .paymentline.selected .payment-name:contains("${paymentMethodName}")`,
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
content: `amount tendered in the line is '${amount}'`,
|
||||
trigger: `.paymentlines .paymentline.selected .payment-amount:contains("${amount}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
totalIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `total is ${amount}`,
|
||||
trigger: `.total:contains("${amount}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
totalDueIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `total due is ${amount}`,
|
||||
trigger: `.payment-status-total-due:contains("${amount}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
isInvoiceButtonChecked() {
|
||||
return [
|
||||
{
|
||||
content: 'check invoice button is checked',
|
||||
trigger: '.js_invoice.highlight',
|
||||
run: () => {},
|
||||
}
|
||||
]
|
||||
}
|
||||
isInvoiceButtonNotChecked() {
|
||||
return [
|
||||
{
|
||||
content: "check invoice button is checked",
|
||||
trigger: ".js_invoice:not(.highlight)",
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {
|
||||
pay(method, amount) {
|
||||
const steps = [];
|
||||
steps.push(...this._do.clickPaymentMethod(method));
|
||||
for (let char of amount.split('')) {
|
||||
steps.push(...this._do.pressNumpad(char));
|
||||
}
|
||||
steps.push(...this._check.validateButtonIsHighlighted());
|
||||
steps.push(...this._do.clickValidate());
|
||||
return steps;
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('PaymentScreen', Do, Check, Execute);
|
||||
});
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ProductConfiguratorTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
pickRadio(name) {
|
||||
return [
|
||||
{
|
||||
content: `picking radio attribute with name ${name}`,
|
||||
trigger: `.product-configurator-popup .attribute-name-cell label:contains('${name}')`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
pickSelect(name) {
|
||||
return [
|
||||
{
|
||||
content: `picking select attribute with name ${name}`,
|
||||
trigger: `.product-configurator-popup .configurator_select:has(option:contains('${name}'))`,
|
||||
run: `text ${name}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
pickColor(name) {
|
||||
return [
|
||||
{
|
||||
content: `picking color attribute with name ${name}`,
|
||||
trigger: `.product-configurator-popup .configurator_color[data-color='${name}']`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
fillCustomAttribute(value) {
|
||||
return [
|
||||
{
|
||||
content: `filling custom attribute with value ${value}`,
|
||||
trigger: `.product-configurator-popup .custom_value`,
|
||||
run: `text ${value}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
confirmAttributes() {
|
||||
return [
|
||||
{
|
||||
content: `confirming product configuration`,
|
||||
trigger: `.product-configurator-popup .button.confirm`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
cancelAttributes() {
|
||||
return [
|
||||
{
|
||||
content: `canceling product configuration`,
|
||||
trigger: `.product-configurator-popup .button.cancel`,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'product configurator is shown',
|
||||
trigger: '.product-configurator-popup:not(:has(.oe_hidden))',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
numberRadioOptions(number) {
|
||||
return [
|
||||
{
|
||||
trigger: `.product-configurator-popup .attribute-name-cell`,
|
||||
run: () => {
|
||||
const radio_options = $('.product-configurator-popup .attribute-name-cell').length;
|
||||
if (radio_options !== number) {
|
||||
throw new Error(`Expected ${number} radio options, got ${radio_options}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('ProductConfigurator', Do, Check);
|
||||
});
|
||||
|
|
@ -1,429 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ProductScreenTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
const { TextAreaPopup } = require('point_of_sale.tour.TextAreaPopupTourMethods');
|
||||
|
||||
class Do {
|
||||
clickDisplayedProduct(name) {
|
||||
return [
|
||||
{
|
||||
content: `click product '${name}'`,
|
||||
trigger: `.product-list .product-name:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickOrderline(name, quantity) {
|
||||
return [
|
||||
{
|
||||
content: `selecting orderline with product '${name}' and quantity '${quantity}'`,
|
||||
trigger: `.order .orderline:not(:has(.selected)) .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
|
||||
},
|
||||
{
|
||||
content: `orderline with product '${name}' and quantity '${quantity}' has been selected`,
|
||||
trigger: `.order .orderline.selected .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickSubcategory(name) {
|
||||
return [
|
||||
{
|
||||
content: `selecting '${name}' subcategory`,
|
||||
trigger: `.products-widget > .products-widget-control .category-simple-button:contains("${name}")`,
|
||||
},
|
||||
{
|
||||
content: `'${name}' subcategory selected`,
|
||||
trigger: `.breadcrumbs .breadcrumb-button:contains("${name}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickHomeCategory() {
|
||||
return [
|
||||
{
|
||||
content: `click Home subcategory`,
|
||||
trigger: `.breadcrumbs .breadcrumb-home`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Press the numpad in sequence based on the given space-separated keys.
|
||||
* NOTE: Maximum of 2 characters because NumberBuffer only allows 2 consecutive
|
||||
* fast inputs. Fast inputs is the case in tours.
|
||||
*
|
||||
* @param {String} keys space-separated numpad keys
|
||||
*/
|
||||
pressNumpad(keys) {
|
||||
const numberChars = '. 0 1 2 3 4 5 6 7 8 9'.split(' ');
|
||||
const modeButtons = 'Qty Price Disc'.split(' ');
|
||||
function generateStep(key) {
|
||||
let trigger;
|
||||
if (numberChars.includes(key)) {
|
||||
trigger = `.numpad .number-char:contains("${key}")`;
|
||||
} else if (modeButtons.includes(key)) {
|
||||
trigger = `.numpad .mode-button:contains("${key}")`;
|
||||
} else if (key === 'Backspace') {
|
||||
trigger = `.numpad .numpad-backspace`;
|
||||
} else if (key === '+/-') {
|
||||
trigger = `.numpad .numpad-minus`;
|
||||
}
|
||||
return {
|
||||
content: `'${key}' pressed in product screen numpad`,
|
||||
trigger,
|
||||
};
|
||||
}
|
||||
return keys.split(' ').map(generateStep);
|
||||
}
|
||||
|
||||
clickPayButton(shouldCheck = true) {
|
||||
const steps = [{ content: 'click pay button', trigger: '.product-screen .actionpad .button.pay' }];
|
||||
if (shouldCheck) {
|
||||
steps.push({
|
||||
content: 'now in payment screen',
|
||||
trigger: '.pos-content .payment-screen',
|
||||
run: () => {},
|
||||
});
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
clickPartnerButton() {
|
||||
return [
|
||||
{ content: 'click customer button', trigger: '.actionpad .button.set-partner' },
|
||||
{
|
||||
content: 'partner screen is shown',
|
||||
trigger: '.pos-content .partnerlist-screen',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickCustomer(name) {
|
||||
return [
|
||||
{
|
||||
content: `select customer '${name}'`,
|
||||
trigger: `.partnerlist-screen .partner-line td:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
clickOrderlineCustomerNoteButton() {
|
||||
return [
|
||||
{
|
||||
content: 'click customer note button',
|
||||
trigger: '.control-buttons .control-button span:contains("Customer Note")',
|
||||
}
|
||||
]
|
||||
}
|
||||
clickRefund() {
|
||||
return [
|
||||
{
|
||||
trigger: '.control-button:contains("Refund")',
|
||||
},
|
||||
];
|
||||
}
|
||||
confirmOpeningPopup() {
|
||||
return [{ trigger: '.opening-cash-control .button:contains("Open session")' }];
|
||||
}
|
||||
clickPricelistButton() {
|
||||
return [{ trigger: '.o_pricelist_button' }];
|
||||
}
|
||||
selectPriceList(name) {
|
||||
return [
|
||||
{
|
||||
content: `select price list '${name}'`,
|
||||
trigger: `.selection-item:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
enterOpeningAmount(amount) {
|
||||
return [
|
||||
{
|
||||
content: 'enter opening amount',
|
||||
trigger: '.cash-input-sub-section > .pos-input',
|
||||
run: 'text ' + amount,
|
||||
},
|
||||
];
|
||||
}
|
||||
changeFiscalPosition(name) {
|
||||
return [
|
||||
{
|
||||
content: 'click fiscal position button',
|
||||
trigger: '.o_fiscal_position_button',
|
||||
},
|
||||
{
|
||||
content: 'fiscal position screen is shown',
|
||||
trigger: `.selection-item:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
scan_barcode(barcode) {
|
||||
return [
|
||||
{
|
||||
content: `input barcode '${barcode}'`,
|
||||
trigger: "input.ean",
|
||||
run: `text ${barcode}`,
|
||||
},
|
||||
{
|
||||
content: `button scan barcode '${barcode}'`,
|
||||
trigger: "li.barcode",
|
||||
run: 'click',
|
||||
}
|
||||
];
|
||||
}
|
||||
scan_ean13_barcode(barcode) {
|
||||
return [
|
||||
{
|
||||
content: `input barcode '${barcode}'`,
|
||||
trigger: "input.ean",
|
||||
run: `text ${barcode}`,
|
||||
},
|
||||
{
|
||||
content: `button scan EAN-13 barcode '${barcode}'`,
|
||||
trigger: "li.custom_ean",
|
||||
run: 'click',
|
||||
}
|
||||
];
|
||||
}
|
||||
clickLotIcon() {
|
||||
return [
|
||||
{
|
||||
content: 'click lot icon',
|
||||
trigger: '.line-lot-icon',
|
||||
},
|
||||
];
|
||||
}
|
||||
enterLotNumber(number) {
|
||||
return [
|
||||
{
|
||||
content: 'enter lot number',
|
||||
trigger: '.list-line-input:first()',
|
||||
run: 'text ' + number,
|
||||
},
|
||||
{
|
||||
content: 'click validate lot number',
|
||||
trigger: '.popup .button.confirm',
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'product screen is shown',
|
||||
trigger: '.product-screen',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
selectedOrderlineHas(name, quantity, price) {
|
||||
const res = [
|
||||
{
|
||||
// check first if the order widget is there and has orderlines
|
||||
content: 'order widget has orderlines',
|
||||
trigger: '.order .orderlines',
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
content: `'${name}' is selected`,
|
||||
trigger: `.order .orderline.selected .product-name:contains("${name}")`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
];
|
||||
if (quantity) {
|
||||
res.push({
|
||||
content: `selected line has ${quantity} quantity`,
|
||||
trigger: `.order .orderline.selected .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
|
||||
run: function () {}, // it's a check
|
||||
});
|
||||
}
|
||||
if (price) {
|
||||
res.push({
|
||||
content: `selected line has total price of ${price}`,
|
||||
trigger: `.order .orderline.selected .product-name:contains("${name}") ~ .price:contains("${price}")`,
|
||||
run: function () {}, // it's a check
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
orderIsEmpty() {
|
||||
return [
|
||||
{
|
||||
content: `order is empty`,
|
||||
trigger: `.order .order-empty`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
productIsDisplayed(name) {
|
||||
return [
|
||||
{
|
||||
content: `'${name}' should be displayed`,
|
||||
trigger: `.product-list .product-name:contains("${name}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
totalAmountIs(amount) {
|
||||
return [
|
||||
{
|
||||
content: `order total amount is '${amount}'`,
|
||||
trigger: `.order-container .order .summary .value:contains("${amount}")`,
|
||||
run: () => {},
|
||||
}
|
||||
]
|
||||
}
|
||||
modeIsActive(mode) {
|
||||
return [
|
||||
{
|
||||
content: `'${mode}' is active`,
|
||||
trigger: `.numpad button.selected-mode:contains('${mode}')`,
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
}
|
||||
orderlineHasCustomerNote(name, quantity, note) {
|
||||
return [
|
||||
{
|
||||
content: `line has ${quantity} quantity`,
|
||||
trigger: `.order .orderline .product-name:contains("${name}") ~ .info-list em:contains("${quantity}")`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: `line has '${note}' as customer note`,
|
||||
trigger: `.order .orderline .info-list .orderline-note:contains("${note}")`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
}
|
||||
checkSecondCashClosingDetailsLineAmount(amount, sign) {
|
||||
return [
|
||||
{
|
||||
content: 'Click close session button',
|
||||
trigger: '.fa-sign-out',
|
||||
},
|
||||
{
|
||||
content: 'Check closing details',
|
||||
trigger: `.cash-overview tr:nth-child(2) td:contains("${amount}")`,
|
||||
run: () => {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: 'Check closing details',
|
||||
trigger: `.cash-overview tr:nth-child(2) .cash-sign:contains("${sign}")`,
|
||||
run: () => {}, // it's a check
|
||||
},
|
||||
];
|
||||
}
|
||||
noDiscountApplied(originalPrice) {
|
||||
return [
|
||||
{
|
||||
content: 'no discount is applied',
|
||||
trigger: `.info:not(:contains(${originalPrice}))`,
|
||||
},
|
||||
];
|
||||
}
|
||||
discountOriginalPriceIs(original_price) {
|
||||
return [
|
||||
{
|
||||
content: `discount original price is shown`,
|
||||
trigger: `s:contains('${original_price}')`,
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
}
|
||||
checkFirstLotNumber(number) {
|
||||
return [
|
||||
{
|
||||
content: 'Check lot number',
|
||||
trigger: `.list-line-input:propValue('${number}')`,
|
||||
run: () => {}, // it's a check
|
||||
},
|
||||
];
|
||||
}
|
||||
checkOrderlinesNumber(number) {
|
||||
return [
|
||||
{
|
||||
content: `check orderlines number`,
|
||||
trigger: `.order .orderlines .orderline`,
|
||||
run: () => {
|
||||
const orderline_amount = $('.order .orderlines .orderline').length;
|
||||
if (orderline_amount !== number) {
|
||||
throw new Error(`Expected ${number} orderlines, got ${orderline_amount}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
checkTaxAmount(number) {
|
||||
return [
|
||||
{
|
||||
content: `check order tax amount`,
|
||||
trigger: `.subentry:contains("${number}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {
|
||||
/**
|
||||
* Create an orderline for the given `productName` and `quantity`.
|
||||
* - If `unitPrice` is provided, price of the product of the created line
|
||||
* is changed to that value.
|
||||
* - If `expectedTotal` is provided, the created orderline (which is the currently
|
||||
* selected orderline) is checked if it contains the correct quantity and total
|
||||
* price.
|
||||
*
|
||||
* @param {string} productName
|
||||
* @param {string} quantity
|
||||
* @param {string} unitPrice
|
||||
* @param {string} expectedTotal
|
||||
*/
|
||||
addOrderline(productName, quantity, unitPrice = undefined, expectedTotal = undefined) {
|
||||
const res = this._do.clickDisplayedProduct(productName);
|
||||
if (unitPrice) {
|
||||
res.push(...this._do.pressNumpad('Price'));
|
||||
res.push(...this._check.modeIsActive('Price'));
|
||||
res.push(...this._do.pressNumpad(unitPrice.toString().split('').join(' ')));
|
||||
res.push(...this._do.pressNumpad('Qty'));
|
||||
res.push(...this._check.modeIsActive('Qty'));
|
||||
}
|
||||
for (let char of (quantity.toString() == '1' ? '' : quantity.toString())) {
|
||||
if ('.0123456789'.includes(char)) {
|
||||
res.push(...this._do.pressNumpad(char));
|
||||
} else if ('-'.includes(char)) {
|
||||
res.push(...this._do.pressNumpad('+/-'));
|
||||
}
|
||||
}
|
||||
if (expectedTotal) {
|
||||
res.push(...this._check.selectedOrderlineHas(productName, quantity, expectedTotal));
|
||||
} else {
|
||||
res.push(...this._check.selectedOrderlineHas(productName, quantity));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
addMultiOrderlines(...list) {
|
||||
const steps = [];
|
||||
for (let [product, qty, price] of list) {
|
||||
steps.push(...this.addOrderline(product, qty, price));
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
addCustomerNote(note) {
|
||||
const res = [];
|
||||
res.push(...this._do.clickOrderlineCustomerNoteButton());
|
||||
res.push(...TextAreaPopup._do.inputText(note));
|
||||
res.push(...TextAreaPopup._do.clickConfirm());
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('ProductScreen', Do, Check, Execute);
|
||||
});
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.ReceiptScreenTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
clickNextOrder() {
|
||||
return [
|
||||
{
|
||||
content: 'go to next screen',
|
||||
trigger: '.receipt-screen .button.next.highlight',
|
||||
},
|
||||
];
|
||||
}
|
||||
setEmail(email) {
|
||||
return [
|
||||
{
|
||||
trigger: '.receipt-screen .input-email input',
|
||||
run: `text ${email}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
clickSend(isHighlighted = true) {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .input-email .send${isHighlighted ? '.highlight' : ''}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
clickBack() {
|
||||
return [
|
||||
{
|
||||
trigger: '.receipt-screen .button.back',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'receipt screen is shown',
|
||||
trigger: '.pos .receipt-screen',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
receiptIsThere() {
|
||||
return [
|
||||
{
|
||||
content: 'there should be the receipt',
|
||||
trigger: '.receipt-screen .pos-receipt',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
totalAmountContains(value) {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .top-content h1:contains("${value}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
emailIsSuccessful() {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .notice .successful`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
customerNoteIsThere(note) {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .orderlines .pos-receipt-left-padding:contains("${note}")`
|
||||
}
|
||||
]
|
||||
}
|
||||
discountAmountIs(value) {
|
||||
return [
|
||||
{
|
||||
trigger: `.pos-receipt>div:contains("Discounts")>span:contains("${value}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
noDiscountAmount() {
|
||||
return [
|
||||
{
|
||||
trigger: `.pos-receipt:not(:contains("Discounts"))`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
noOrderlineContainsDiscount() {
|
||||
return [
|
||||
{
|
||||
trigger: `.orderlines:not(:contains('->'))`,
|
||||
run: () => { },
|
||||
},
|
||||
];
|
||||
}
|
||||
trackingMethodIsLot() {
|
||||
return [
|
||||
{
|
||||
content: `tracking method is Lot`,
|
||||
trigger: `li:contains("Lot Number")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {
|
||||
nextOrder() {
|
||||
return [...this._check.isShown(), ...this._do.clickNextOrder()];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('ReceiptScreen', Do, Check, Execute);
|
||||
});
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.SelectionPopupTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
clickItem(name) {
|
||||
return [
|
||||
{
|
||||
content: `click selection '${name}'`,
|
||||
trigger: `.selection-item:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
hasSelectionItem(name) {
|
||||
return [
|
||||
{
|
||||
content: `selection popup has '${name}'`,
|
||||
trigger: `.selection-item:contains("${name}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'selection popup is shown',
|
||||
trigger: '.modal-dialog .popup-selection',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('SelectionPopup', Do, Check);
|
||||
});
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.TextAreaPopupTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
inputText(val) {
|
||||
return [
|
||||
{
|
||||
content: `input text '${val}'`,
|
||||
trigger: `.modal-dialog .popup-textarea textarea`,
|
||||
run: `text ${val}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
clickConfirm() {
|
||||
return [
|
||||
{
|
||||
content: 'confirm text input popup',
|
||||
trigger: '.modal-dialog .confirm',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'text input popup is shown',
|
||||
trigger: '.modal-dialog .popup-textarea',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('TextAreaPopup', Do, Check);
|
||||
});
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.TextInputPopupTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
inputText(val) {
|
||||
return [
|
||||
{
|
||||
content: `input text '${val}'`,
|
||||
trigger: `.modal-dialog .popup-textinput input`,
|
||||
run: `text ${val}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
clickConfirm() {
|
||||
return [
|
||||
{
|
||||
content: 'confirm text input popup',
|
||||
trigger: '.modal-dialog .confirm',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
isShown() {
|
||||
return [
|
||||
{
|
||||
content: 'text input popup is shown',
|
||||
trigger: '.modal-dialog .popup-textinput',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('TextInputPopup', Do, Check);
|
||||
});
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.TicketScreenTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
|
||||
class Do {
|
||||
clickNewTicket() {
|
||||
return [{ trigger: '.ticket-screen .highlight' }];
|
||||
}
|
||||
clickDiscard() {
|
||||
return [{ trigger: '.ticket-screen button.discard' }];
|
||||
}
|
||||
selectOrder(orderName) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .order-row > .col:nth-child(2):contains("${orderName}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
deleteOrder(orderName) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .orders > .order-row > .col:contains("${orderName}") ~ .col[name="delete"]`,
|
||||
},
|
||||
];
|
||||
}
|
||||
selectFilter(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.pos-search-bar .filter`,
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .filter ul`,
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .filter ul li:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
search(field, searchWord) {
|
||||
return [
|
||||
{
|
||||
trigger: '.pos-search-bar input',
|
||||
run: `text ${searchWord}`,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* Manually trigger keyup event to show the search field list
|
||||
* because the previous step do not trigger keyup event.
|
||||
*/
|
||||
trigger: '.pos-search-bar input',
|
||||
run: function () {
|
||||
document
|
||||
.querySelector('.pos-search-bar input')
|
||||
.dispatchEvent(new KeyboardEvent('keyup', { key: '' }));
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: `.pos-search-bar .search ul li:contains("${field}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
settleTips() {
|
||||
return [
|
||||
{
|
||||
trigger: '.ticket-screen .buttons .settle-tips',
|
||||
},
|
||||
];
|
||||
}
|
||||
clickControlButton(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .control-button:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
clickOrderline(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .orderline:not(:has(.selected)) .product-name:contains("${name}")`,
|
||||
},
|
||||
{
|
||||
trigger: `.ticket-screen .orderline.selected .product-name:contains("${name}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
pressNumpad(key) {
|
||||
let trigger;
|
||||
if ('.0123456789'.includes(key)) {
|
||||
trigger = `.numpad .number-char:contains("${key}")`;
|
||||
} else if (key === 'Backspace') {
|
||||
trigger = `.numpad .numpad-backspace`;
|
||||
} else if (key === '+/-') {
|
||||
trigger = `.numpad .numpad-minus`;
|
||||
}
|
||||
return [
|
||||
{
|
||||
trigger,
|
||||
},
|
||||
];
|
||||
}
|
||||
confirmRefund() {
|
||||
return [
|
||||
{
|
||||
trigger: '.ticket-screen .button.pay',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
checkStatus(orderName, status) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .order-row > .col:nth-child(2):contains("${orderName}") ~ .col:nth-child(6):contains(${status})`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Check if the nth row contains the given string.
|
||||
* Note that 1st row is the header-row.
|
||||
*/
|
||||
nthRowContains(n, string) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .orders > .order-row:nth-child(${n}):contains("${string}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
noNewTicketButton() {
|
||||
return [
|
||||
{
|
||||
trigger: '.ticket-screen .controls .buttons:nth-child(1):has(.discard)',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
orderWidgetIsNotEmpty() {
|
||||
return [
|
||||
{
|
||||
trigger: '.ticket-screen:not(:has(.order-empty))',
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
filterIs(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .pos-search-bar .filter span:contains("${name}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
partnerIs(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .set-partner:contains("${name}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
toRefundTextContains(text) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .to-refund-highlight:contains("${text}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
refundedNoteContains(text) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .refund-note:contains("${text}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
tipContains(amount) {
|
||||
return [
|
||||
{
|
||||
trigger: `.ticket-screen .tip-cell:contains("${amount}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {}
|
||||
|
||||
return createTourMethods('TicketScreen', Do, Check, Execute);
|
||||
});
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
odoo.define('point_of_sale.tour.utils', function (require) {
|
||||
'use strict';
|
||||
|
||||
const config = require('web.config');
|
||||
|
||||
/**
|
||||
* USAGE
|
||||
* -----
|
||||
*
|
||||
* ```
|
||||
* const { startSteps, getSteps, createTourMethods } = require('point_of_sale.utils');
|
||||
* const { Other } = require('point_of_sale.tour.OtherMethods');
|
||||
*
|
||||
* // 1. Define classes Do, Check and Execute having methods that
|
||||
* // each return array of tour steps.
|
||||
* class Do {
|
||||
* click() {
|
||||
* return [{ content: 'click button', trigger: '.button' }];
|
||||
* }
|
||||
* }
|
||||
* class Check {
|
||||
* isHighligted() {
|
||||
* return [{ content: 'button is highlighted', trigger: '.button.highlight', run: () => {} }];
|
||||
* }
|
||||
* }
|
||||
* // Notice that Execute has access to methods defined in Do and Check classes
|
||||
* // Also, we can compose steps from other module.
|
||||
* class Execute {
|
||||
* complexSteps() {
|
||||
* return [...this._do.click(), ...this._check.isHighlighted(), ...Other._exec.complicatedSteps()];
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // 2. Instantiate these class definitions using `createTourMethods`.
|
||||
* // The returned object gives access to the defined methods above
|
||||
* // thru the do, check and exec properties.
|
||||
* // - do gives access to the methods defined in Do class
|
||||
* // - check gives access to the methods defined in Check class
|
||||
* // - exec gives access to the methods defined in Execute class
|
||||
* const Screen = createTourMethods('Screen', Do, Check, Execute);
|
||||
*
|
||||
* // 3. Call `startSteps` to start empty steps.
|
||||
* startSteps();
|
||||
*
|
||||
* // 4. Call the tour methods to populate the steps created by `startSteps`.
|
||||
* Screen.do.click(); // return of this method call is added to steps created by startSteps
|
||||
* Screen.check.isHighlighted() // same as above
|
||||
* Screen.exec.complexSteps() // same as above
|
||||
*
|
||||
* // 5. Call `getSteps` which returns the generated tour steps.
|
||||
* const steps = getSteps();
|
||||
* ```
|
||||
*/
|
||||
let steps = [];
|
||||
|
||||
function startSteps() {
|
||||
// always start by waiting for loading to finish
|
||||
steps = [
|
||||
{
|
||||
content: 'wait for loading to finish',
|
||||
trigger: 'body:not(:has(.loader))',
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
// this is the method decorator
|
||||
// when the method is called, the generated steps are added
|
||||
// to steps
|
||||
const methodProxyHandler = {
|
||||
apply(target, thisArg, args) {
|
||||
const res = target.call(thisArg, ...args);
|
||||
if (config.isDebug()) {
|
||||
// This step is added before the real steps.
|
||||
// Very useful when debugging because we know which
|
||||
// method call failed and what were the parameters.
|
||||
const constructor = thisArg.constructor.name.split(' ')[1];
|
||||
const methodName = target.name.split(' ')[1];
|
||||
const argList = args
|
||||
.map((a) => (typeof a === 'string' ? `'${a}'` : `${a}`))
|
||||
.join(', ');
|
||||
steps.push({
|
||||
content: `DOING "${constructor}.${methodName}(${argList})"`,
|
||||
trigger: '.pos',
|
||||
run: () => {},
|
||||
});
|
||||
}
|
||||
steps.push(...res);
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
// we proxy get of the method to decorate the method call
|
||||
const proxyHandler = {
|
||||
get(target, key) {
|
||||
const method = target[key];
|
||||
if (!method) {
|
||||
throw new Error(`Tour method '${key}' is not available.`);
|
||||
}
|
||||
return new Proxy(method.bind(target), methodProxyHandler);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an object with `do`, `check` and `exec` properties which are instances of
|
||||
* the given `Do`, `Check` and `Execute` classes, respectively. Calling methods
|
||||
* automatically adds the returned steps to the steps created by `startSteps`.
|
||||
*
|
||||
* There are however underscored version (_do, _check, _exec).
|
||||
* Calling methods thru the underscored version does not automatically
|
||||
* add the returned steps to the current steps array. Useful when composing
|
||||
* steps from other methods.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} Do class containing methods which return array of tour steps
|
||||
* @param {Function} Check similar to Do class but the steps are mainly for checking
|
||||
* @param {Function} Execute class containing methods which return array of tour steps
|
||||
* but has access to methods of Do and Check classes via .do and .check,
|
||||
* respectively. Here, we define methods that return tour steps based
|
||||
* on the combination of steps from Do and Check.
|
||||
*/
|
||||
function createTourMethods(name, Do, Check = class {}, Execute = class {}) {
|
||||
Object.defineProperty(Do, 'name', { value: `${name}.do` });
|
||||
Object.defineProperty(Check, 'name', { value: `${name}.check` });
|
||||
Object.defineProperty(Execute, 'name', {
|
||||
value: `${name}.exec`,
|
||||
});
|
||||
const methods = { do: new Do(), check: new Check(), exec: new Execute() };
|
||||
// Allow Execute to have access to methods defined in Do and Check
|
||||
// via do and exec, respectively.
|
||||
methods.exec._do = methods.do;
|
||||
methods.exec._check = methods.check;
|
||||
return {
|
||||
Do,
|
||||
Check,
|
||||
Execute,
|
||||
[name]: {
|
||||
do: new Proxy(methods.do, proxyHandler),
|
||||
check: new Proxy(methods.check, proxyHandler),
|
||||
exec: new Proxy(methods.exec, proxyHandler),
|
||||
_do: methods.do,
|
||||
_check: methods.check,
|
||||
_exec: methods.exec,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { startSteps, getSteps, createTourMethods };
|
||||
});
|
||||
|
|
@ -1,408 +0,0 @@
|
|||
/* global posmodel */
|
||||
odoo.define('point_of_sale.tour.pricelist', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Tour = require('web_tour.tour');
|
||||
var utils = require('web.utils');
|
||||
var round_di = utils.round_decimals;
|
||||
|
||||
function assert (condition, message) {
|
||||
if (! condition) {
|
||||
throw message || "Assertion failed";
|
||||
}
|
||||
}
|
||||
|
||||
function assertProductPrice(product, pricelist_name, quantity, expected_price) {
|
||||
return function () {
|
||||
var pricelist = _.findWhere(posmodel.pricelists, {name: pricelist_name});
|
||||
var frontend_price = product.get_price(pricelist, quantity);
|
||||
frontend_price = round_di(frontend_price, posmodel.dp['Product Price']);
|
||||
|
||||
var diff = Math.abs( expected_price - frontend_price );
|
||||
|
||||
assert(diff < 0.001,
|
||||
JSON.stringify({
|
||||
product: product.id,
|
||||
product_display_name: product.display_name,
|
||||
pricelist_name: pricelist_name,
|
||||
quantity: quantity
|
||||
}) + ' DOESN\'T MATCH -> ' + expected_price + ' != ' + frontend_price);
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
// The global posmodel is only present when the posmodel is instanciated
|
||||
// So, wait for everythiong to be loaded
|
||||
var steps = [{ // Leave category displayed by default
|
||||
content: 'waiting for loading to finish',
|
||||
extra_trigger: 'body .pos:not(:has(.loader))', // Pos has finished loading
|
||||
trigger: 'body:not(:has(.o_loading_indicator))', // WebClient has finished Loading
|
||||
run: function () {
|
||||
var product_wall_shelf = posmodel.db.search_product_in_category(0, 'Wall Shelf Unit')[0];
|
||||
var product_small_shelf = posmodel.db.search_product_in_category(0, 'Small Shelf')[0];
|
||||
var product_magnetic_board = posmodel.db.search_product_in_category(0, 'Magnetic Board')[0];
|
||||
var product_monitor_stand = posmodel.db.search_product_in_category(0, 'Monitor Stand')[0];
|
||||
var product_desk_pad = posmodel.db.search_product_in_category(0, 'Desk Pad')[0];
|
||||
var product_letter_tray = posmodel.db.search_product_in_category(0, 'Letter Tray')[0];
|
||||
var product_whiteboard = posmodel.db.search_product_in_category(0, 'Whiteboard')[0];
|
||||
|
||||
assertProductPrice(product_letter_tray, 'Public Pricelist', 0, 4.8)()
|
||||
.then(assertProductPrice(product_letter_tray, 'Public Pricelist', 1, 4.8))
|
||||
.then(assertProductPrice(product_letter_tray, 'Fixed', 1, 1))
|
||||
.then(assertProductPrice(product_wall_shelf, 'Fixed', 1, 2))
|
||||
.then(assertProductPrice(product_small_shelf, 'Fixed', 1, 13.95))
|
||||
.then(assertProductPrice(product_wall_shelf, 'Percentage', 1, 0))
|
||||
.then(assertProductPrice(product_small_shelf, 'Percentage', 1, 0.03))
|
||||
.then(assertProductPrice(product_magnetic_board, 'Percentage', 1, 1.98))
|
||||
.then(assertProductPrice(product_wall_shelf, 'Formula', 1, 6.86))
|
||||
.then(assertProductPrice(product_small_shelf, 'Formula', 1, 2.99))
|
||||
.then(assertProductPrice(product_magnetic_board, 'Formula', 1, 11.98))
|
||||
.then(assertProductPrice(product_monitor_stand, 'Formula', 1, 8.19))
|
||||
.then(assertProductPrice(product_desk_pad, 'Formula', 1, 6.98))
|
||||
.then(assertProductPrice(product_wall_shelf, 'min_quantity ordering', 1, 2))
|
||||
.then(assertProductPrice(product_wall_shelf, 'min_quantity ordering', 2, 1))
|
||||
.then(assertProductPrice(product_letter_tray, 'Category vs no category', 1, 2))
|
||||
.then(assertProductPrice(product_letter_tray, 'Category', 1, 2))
|
||||
.then(assertProductPrice(product_wall_shelf, 'Product template', 1, 1))
|
||||
.then(assertProductPrice(product_wall_shelf, 'Dates', 1, 2))
|
||||
.then(assertProductPrice(product_small_shelf, 'Pricelist base rounding', 1, 13.95))
|
||||
.then(assertProductPrice(product_whiteboard, 'Public Pricelist', 1, 3.2))
|
||||
.then(function () {
|
||||
$('.pos').addClass('done-testing');
|
||||
});
|
||||
},
|
||||
}, {
|
||||
trigger: '.opening-cash-control .button:contains("Open session")',
|
||||
}];
|
||||
|
||||
steps = steps.concat([{
|
||||
content: "wait for unit tests to finish",
|
||||
trigger: ".pos.done-testing",
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "click category switch",
|
||||
trigger: ".breadcrumb-home",
|
||||
run: 'click',
|
||||
}, {
|
||||
content: "click pricelist button",
|
||||
trigger: ".control-button.o_pricelist_button",
|
||||
}, {
|
||||
content: "verify default pricelist is set",
|
||||
trigger: ".selection-item.selected:contains('Public Pricelist')",
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "select fixed pricelist",
|
||||
trigger: ".selection-item:contains('Fixed')",
|
||||
}, {
|
||||
content: "open partner list",
|
||||
trigger: "button.set-partner",
|
||||
}, {
|
||||
content: "select Deco Addict",
|
||||
trigger: ".partner-line:contains('Deco Addict')",
|
||||
}, {
|
||||
content: "click pricelist button",
|
||||
trigger: ".control-button.o_pricelist_button",
|
||||
}, {
|
||||
content: "verify pricelist changed",
|
||||
trigger: ".selection-item.selected:contains('Public Pricelist')",
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "cancel pricelist dialog",
|
||||
trigger: ".button.cancel:visible",
|
||||
}, {
|
||||
content: "open customer list",
|
||||
trigger: "button.set-partner",
|
||||
}, {
|
||||
content: "select Lumber Inc",
|
||||
trigger: ".partner-line:contains('Lumber Inc')",
|
||||
}, {
|
||||
content: "click pricelist button",
|
||||
trigger: ".control-button.o_pricelist_button",
|
||||
}, {
|
||||
content: "verify pricelist remained public pricelist ('Not loaded' is not available)",
|
||||
trigger: ".selection-item.selected:contains('Public Pricelist')",
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "cancel pricelist dialog",
|
||||
trigger: ".button.cancel:visible",
|
||||
}, {
|
||||
content: "click pricelist button",
|
||||
trigger: ".control-button.o_pricelist_button",
|
||||
}, {
|
||||
content: "select fixed pricelist",
|
||||
trigger: ".selection-item:contains('min_quantity ordering')",
|
||||
}, {
|
||||
content: "order 1 kg shelf",
|
||||
trigger: ".product:contains('Wall Shelf')",
|
||||
}, {
|
||||
content: "change qty to 2 kg",
|
||||
trigger: ".numpad button.input-button:visible:contains('2')",
|
||||
}, {
|
||||
content: "qty of Wall Shelf line should be 2",
|
||||
trigger: ".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf')",
|
||||
extra_trigger: ".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf') ~ .info-list .info em:contains('2.0')",
|
||||
run: function() {},
|
||||
}, {
|
||||
content: "verify that unit price of shelf changed to $1",
|
||||
trigger: ".total > .value:contains('$ 2.00')",
|
||||
run: function() {},
|
||||
}, {
|
||||
content: "order different shelf",
|
||||
trigger: ".product:contains('Small Shelf')",
|
||||
}, {
|
||||
content: "Small Shelf line should be selected with quantity 1",
|
||||
trigger: ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf')",
|
||||
extra_trigger: ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf') ~ .info-list .info em:contains('1.0')",
|
||||
run: function() {}
|
||||
}, {
|
||||
content: "change to price mode",
|
||||
trigger: ".numpad button:contains('Price')",
|
||||
}, {
|
||||
content: "make sure price mode is activated",
|
||||
trigger: ".numpad button.selected-mode:contains('Price')",
|
||||
run: function() {},
|
||||
}, {
|
||||
content: "manually override the unit price of these shelf to $5",
|
||||
trigger: ".numpad button.input-button:visible:contains('5')",
|
||||
}, {
|
||||
content: "Small Shelf line should be selected with unit price of 5",
|
||||
trigger: ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf')",
|
||||
extra_trigger: ".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf') ~ .price:contains('5.0')",
|
||||
}, {
|
||||
content: "change back to qty mode",
|
||||
trigger: ".numpad button:contains('Qty')",
|
||||
}, {
|
||||
content: "make sure qty mode is activated",
|
||||
trigger: ".numpad button.selected-mode:contains('Qty')",
|
||||
run: function() {},
|
||||
}, {
|
||||
content: "click pricelist button",
|
||||
trigger: ".control-button.o_pricelist_button",
|
||||
}, {
|
||||
content: "select public pricelist",
|
||||
trigger: ".selection-item:contains('Public Pricelist')",
|
||||
}, {
|
||||
content: "verify that the boni shelf have been recomputed and the shelf have not (their price was manually overridden)",
|
||||
trigger: ".total > .value:contains('$ 8.96')",
|
||||
}, {
|
||||
content: "click pricelist button",
|
||||
trigger: ".control-button.o_pricelist_button",
|
||||
}, {
|
||||
content: "select fixed pricelist",
|
||||
trigger: ".selection-item:contains('min_quantity ordering')",
|
||||
}, {
|
||||
content: "close the Point of Sale frontend",
|
||||
trigger: ".header-button",
|
||||
}, {
|
||||
content: "confirm closing the frontend",
|
||||
trigger: ".header-button",
|
||||
run: function() {}, //it's a check,
|
||||
}]);
|
||||
|
||||
Tour.register('pos_pricelist', { test: true, url: '/pos/ui' }, steps);
|
||||
});
|
||||
|
||||
odoo.define('point_of_sale.tour.acceptance', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Tour = require("web_tour.tour");
|
||||
|
||||
function add_product_to_order(product_name) {
|
||||
return [{
|
||||
content: 'buy ' + product_name,
|
||||
trigger: '.product-list .product-name:contains("' + product_name + '")',
|
||||
}, {
|
||||
content: 'the ' + product_name + ' have been added to the order',
|
||||
trigger: '.order .product-name:contains("' + product_name + '")',
|
||||
run: function () {},
|
||||
}];
|
||||
}
|
||||
|
||||
function set_fiscal_position_on_order(fp_name) {
|
||||
return [{
|
||||
content: 'set fiscal position',
|
||||
trigger: '.control-button.o_fiscal_position_button',
|
||||
}, {
|
||||
content: 'choose fiscal position ' + fp_name + ' to add to the order',
|
||||
trigger: '.popups .popup .selection .selection-item:contains("' + fp_name + '")',
|
||||
}, {
|
||||
content: 'the fiscal position ' + fp_name + ' has been set to the order',
|
||||
trigger: '.control-button.o_fiscal_position_button:contains("' + fp_name + '")',
|
||||
run: function () {},
|
||||
}];
|
||||
}
|
||||
|
||||
function press_payment_numpad(val) {
|
||||
return [{
|
||||
content: `press ${val} on payment screen numpad`,
|
||||
trigger: `.payment-numpad .input-button:contains("${val}"):visible`,
|
||||
}]
|
||||
}
|
||||
|
||||
function press_product_numpad(val) {
|
||||
return [{
|
||||
content: `press ${val} on product screen numpad`,
|
||||
trigger: `.numpad .input-button:contains("${val}"):visible`,
|
||||
}]
|
||||
}
|
||||
|
||||
function selected_payment_has(name, val) {
|
||||
return [{
|
||||
content: `selected payment is ${name} and has ${val}`,
|
||||
trigger: `.paymentlines .paymentline.selected .payment-name:contains("${name}")`,
|
||||
extra_trigger: `.paymentlines .paymentline.selected .payment-name:contains("${name}") ~ .payment-amount:contains("${val}")`,
|
||||
run: function () {},
|
||||
}]
|
||||
}
|
||||
|
||||
function selected_orderline_has({ product, price = null, quantity = null }) {
|
||||
const result = [];
|
||||
if (price !== null) {
|
||||
result.push({
|
||||
content: `Selected line has product '${product}' and price '${price}'`,
|
||||
trigger: `.order-container .orderlines .orderline.selected .product-name:contains("${product}") ~ span.price:contains("${price}")`,
|
||||
run: function () {},
|
||||
});
|
||||
}
|
||||
if (quantity !== null) {
|
||||
result.push({
|
||||
content: `Selected line has product '${product}' and quantity '${quantity}'`,
|
||||
trigger: `.order-container .orderlines .orderline.selected .product-name:contains('${product}') ~ .info-list .info em:contains('${quantity}')`,
|
||||
run: function () {},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function verify_order_total(total_str) {
|
||||
return [{
|
||||
content: 'order total contains ' + total_str,
|
||||
trigger: '.order .total .value:contains("' + total_str + '")',
|
||||
run: function () {}, // it's a check
|
||||
}];
|
||||
}
|
||||
|
||||
function goto_payment_screen_and_select_payment_method() {
|
||||
return [{
|
||||
content: "go to payment screen",
|
||||
trigger: '.button.pay',
|
||||
}, {
|
||||
content: "pay with cash",
|
||||
trigger: '.paymentmethod:contains("Cash")',
|
||||
}];
|
||||
}
|
||||
|
||||
function finish_order() {
|
||||
return [{
|
||||
content: "validate the order",
|
||||
trigger: '.payment-screen .button.next.highlight:visible',
|
||||
}, {
|
||||
content: "verify that the order has been successfully sent to the backend",
|
||||
trigger: ".js_connected:visible",
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "click Next Order",
|
||||
trigger: '.receipt-screen .button.next.highlight:visible',
|
||||
}, {
|
||||
content: "check if we left the receipt screen",
|
||||
trigger: '.pos-content .screen:not(:has(.receipt-screen))',
|
||||
run: function () {},
|
||||
}];
|
||||
}
|
||||
|
||||
var steps = [{
|
||||
content: 'waiting for loading to finish',
|
||||
trigger: 'body:not(:has(.loader))',
|
||||
run: function () {},
|
||||
}, { // Leave category displayed by default
|
||||
content: "click category switch",
|
||||
trigger: ".breadcrumb-home",
|
||||
}];
|
||||
|
||||
steps = steps.concat(add_product_to_order('Desk Organizer'));
|
||||
steps = steps.concat(verify_order_total('5.10'));
|
||||
|
||||
steps = steps.concat(add_product_to_order('Desk Organizer'));
|
||||
steps = steps.concat(verify_order_total('10.20'));
|
||||
steps = steps.concat(goto_payment_screen_and_select_payment_method());
|
||||
|
||||
/* add payment line of only 5.20
|
||||
status:
|
||||
order-total := 10.20
|
||||
total-payment := 11.70
|
||||
expect:
|
||||
remaining := 0.00
|
||||
change := 1.50
|
||||
*/
|
||||
steps = steps.concat(press_payment_numpad('5'));
|
||||
steps = steps.concat(selected_payment_has('Cash', '5.0'));
|
||||
steps = steps.concat([{
|
||||
content: "verify remaining",
|
||||
trigger: '.payment-status-remaining .amount:contains("5.20")',
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "verify change",
|
||||
trigger: '.payment-status-change .amount:contains("0.00")',
|
||||
run: function () {},
|
||||
}]);
|
||||
|
||||
/* make additional payment line of 6.50
|
||||
status:
|
||||
order-total := 10.20
|
||||
total-payment := 11.70
|
||||
expect:
|
||||
remaining := 0.00
|
||||
change := 1.50
|
||||
*/
|
||||
steps = steps.concat([{
|
||||
content: "pay with cash",
|
||||
trigger: '.paymentmethod:contains("Cash")',
|
||||
}]);
|
||||
steps = steps.concat(selected_payment_has('Cash', '5.2'));
|
||||
steps = steps.concat(press_payment_numpad('6'))
|
||||
steps = steps.concat(selected_payment_has('Cash', '6.0'));
|
||||
steps = steps.concat([{
|
||||
content: "verify remaining",
|
||||
trigger: '.payment-status-remaining .amount:contains("0.00")',
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "verify change",
|
||||
trigger: '.payment-status-change .amount:contains("0.80")',
|
||||
run: function () {},
|
||||
}]);
|
||||
|
||||
steps = steps.concat(finish_order());
|
||||
|
||||
// test opw-672118 orderline subtotal rounding
|
||||
steps = steps.concat(add_product_to_order('Desk Organizer'));
|
||||
steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '1.0'}));
|
||||
steps = steps.concat(press_product_numpad('.'))
|
||||
steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '0.0', price: '0.0'}));
|
||||
steps = steps.concat(press_product_numpad('9'))
|
||||
steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '0.9', price: '4.59'}));
|
||||
steps = steps.concat(press_product_numpad('9'))
|
||||
steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '0.99', price: '5.05'}));
|
||||
steps = steps.concat(goto_payment_screen_and_select_payment_method());
|
||||
steps = steps.concat(selected_payment_has('Cash', '5.05'));
|
||||
steps = steps.concat(finish_order());
|
||||
|
||||
// Test fiscal position one2many map (align with backend)
|
||||
steps = steps.concat(add_product_to_order('Letter Tray'));
|
||||
steps = steps.concat(selected_orderline_has({product: 'Letter Tray', quantity: '1.0'}));
|
||||
steps = steps.concat(verify_order_total('5.28'));
|
||||
steps = steps.concat(set_fiscal_position_on_order('FP-POS-2M'));
|
||||
steps = steps.concat(verify_order_total('5.52'));
|
||||
|
||||
steps = steps.concat([{
|
||||
content: "open closing the Point of Sale frontend popup",
|
||||
trigger: ".header-button",
|
||||
}, {
|
||||
content: "close the Point of Sale frontend",
|
||||
trigger: ".close-pos-popup .button:contains('Discard')",
|
||||
run: function() {}, //it's a check,
|
||||
}]);
|
||||
|
||||
Tour.register('pos_basic_order', { test: true, url: '/pos/ui' }, steps);
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { expectFormattedPrice, setupPosEnv } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { getFilledOrderForPriceCheck } from "./utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("Taxes object should contain no discount values", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
order.lines[0].setDiscount(10);
|
||||
order.lines[1].setDiscount(20);
|
||||
|
||||
const details = order.prices.taxDetails;
|
||||
const line1 = order.lines[0].prices;
|
||||
const line2 = order.lines[1].prices;
|
||||
|
||||
// Order prices
|
||||
expect(details.base_amount).toBe(980); // Base amount is 980 = (1000 - 10%) + (100 - 20%)
|
||||
expect(details.tax_amount).toBe(257); // Tax amount is 257 = (250 - 10%) + (15 - 20%) + (25 - 20%)
|
||||
expect(details.total_amount).toBe(1237); // Total amount is 1237 = 980 + 257
|
||||
|
||||
// Formatted prices
|
||||
expectFormattedPrice(order.currencyDisplayPrice, "$ 1,237.00");
|
||||
expectFormattedPrice(order.currencyAmountTaxes, "$ 257.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPrice, "$ 1,125.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPriceUnit, "$ 1,125.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPriceUnitExcl, "$ 900.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPrice, "$ 112.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPriceUnit, "$ 112.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPriceUnitExcl, "$ 80.00");
|
||||
|
||||
// First line (25% on 1000) - no discount
|
||||
expect(line1.no_discount_total_included).toBe(1250);
|
||||
expect(line1.no_discount_total_excluded).toBe(1000);
|
||||
expect(line1.no_discount_taxes_data[0].tax_amount).toBe(250);
|
||||
expect(line1.no_discount_taxes_data[0].tax.amount).toBe(25);
|
||||
|
||||
// Second line (15% + 25% on 100) - no discount
|
||||
expect(line2.no_discount_total_included).toBe(140);
|
||||
expect(line2.no_discount_total_excluded).toBe(100);
|
||||
expect(line2.no_discount_taxes_data[0].tax_amount).toBe(15);
|
||||
expect(line2.no_discount_taxes_data[0].tax.amount).toBe(15);
|
||||
expect(line2.no_discount_taxes_data[1].tax_amount).toBe(25);
|
||||
expect(line2.no_discount_taxes_data[1].tax.amount).toBe(25);
|
||||
});
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
/**
|
||||
* This file contains old tour tests related to accounting that were migrated to Hoot.
|
||||
* These tours were not checking anything on the Python side, so they were simply
|
||||
* converted to Hoot tests without any additional checks.
|
||||
*/
|
||||
|
||||
import { test, expect } from "@odoo/hoot";
|
||||
import { setupPosEnv } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { prepareRoundingVals } from "./utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("[Old Tour] pos_basic_order_01_multi_payment_and_change", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const product1 = store.models["product.template"].get(15);
|
||||
product1.list_price = 5.1;
|
||||
product1.product_variant_ids[0].lst_price = 5.1;
|
||||
product1.taxes_id = [];
|
||||
|
||||
const cashPm = store.models["pos.payment.method"].find((pm) => pm.is_cash_count);
|
||||
const cardPm = store.models["pos.payment.method"].find((pm) => !pm.is_cash_count);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
// Add products
|
||||
await store.addLineToOrder({ product_tmpl_id: product1, qty: 2 }, order);
|
||||
order.addPaymentline(cashPm);
|
||||
order.payment_ids[0].setAmount(5);
|
||||
expect(order.remainingDue).toBe(5.2);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
order.payment_ids[1].setAmount(6);
|
||||
expect(order.change).toBe(-0.8);
|
||||
});
|
||||
|
||||
test("[Old Tour] PaymentScreenRoundingHalfUp", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const product1_2 = store.models["product.template"].get(12);
|
||||
const product1_25 = store.models["product.template"].get(13);
|
||||
const product1_40 = store.models["product.template"].get(14);
|
||||
const { cashPm } = prepareRoundingVals(store, 0.5, "HALF-UP", true);
|
||||
|
||||
product1_2.list_price = 1.2;
|
||||
product1_2.product_variant_ids[0].lst_price = 1.2;
|
||||
product1_2.taxes_id = [];
|
||||
product1_25.list_price = 1.25;
|
||||
product1_25.product_variant_ids[0].lst_price = 1.25;
|
||||
product1_25.taxes_id = [];
|
||||
product1_40.list_price = 1.4;
|
||||
product1_40.product_variant_ids[0].lst_price = 1.4;
|
||||
product1_40.taxes_id = [];
|
||||
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
await store.addLineToOrder({ product_tmpl_id: product1_2, qty: 1 }, order);
|
||||
expect(order.totalDue).toBe(1.2);
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.amountPaid).toBe(1.0);
|
||||
expect(order.appliedRounding).toBe(-0.2);
|
||||
expect(order.change).toBe(0.0);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
await store.addLineToOrder({ product_tmpl_id: product1_25, qty: 1 }, order2);
|
||||
expect(order2.totalDue).toBe(1.25);
|
||||
order2.addPaymentline(cashPm);
|
||||
expect(order2.amountPaid).toBe(1.5);
|
||||
expect(order2.appliedRounding).toBe(0.25);
|
||||
expect(order2.change).toBe(0.0);
|
||||
|
||||
const order3 = store.addNewOrder();
|
||||
order3.pricelist_id = false;
|
||||
await store.addLineToOrder({ product_tmpl_id: product1_40, qty: 1 }, order3);
|
||||
expect(order3.totalDue).toBe(1.4);
|
||||
order3.addPaymentline(cashPm);
|
||||
expect(order3.amountPaid).toBe(1.5);
|
||||
expect(order3.appliedRounding).toBe(0.1);
|
||||
expect(order3.change).toBe(0.0);
|
||||
|
||||
const order4 = store.addNewOrder();
|
||||
order4.pricelist_id = false;
|
||||
await store.addLineToOrder({ product_tmpl_id: product1_2, qty: 1 }, order4);
|
||||
expect(order4.totalDue).toBe(1.2);
|
||||
order4.addPaymentline(cashPm);
|
||||
order4.payment_ids[0].setAmount(2);
|
||||
expect(order4.amountPaid).toBe(2.0);
|
||||
expect(order4.change).toBe(-1.0);
|
||||
});
|
||||
|
||||
const prepareProduct = (store) => {
|
||||
const product = store.models["product.template"].get(15);
|
||||
const tax15 = store.models["account.tax"].get(1);
|
||||
product.list_price = 13.67;
|
||||
product.product_variant_ids[0].lst_price = 13.67;
|
||||
product.taxes_id = [tax15];
|
||||
return product;
|
||||
};
|
||||
|
||||
test("[Old Tour] test_cash_rounding_halfup_add_invoice_line_not_only_round_cash_method", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", false);
|
||||
const product = prepareProduct(store);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
|
||||
expect(order.displayPrice).toBe(15.72);
|
||||
expect(order.priceExcl).toBe(13.67);
|
||||
expect(order.totalDue).toBe(15.7);
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.amountPaid).toBe(15.7);
|
||||
expect(order.appliedRounding).toBe(-0.02);
|
||||
expect(order.change).toBe(0.0);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
order.is_refund = true;
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: -1 }, order2);
|
||||
expect(order2.displayPrice).toBe(-15.72);
|
||||
expect(order2.priceExcl).toBe(-13.67);
|
||||
expect(order2.totalDue).toBe(-15.7);
|
||||
order2.addPaymentline(cardPm);
|
||||
expect(order2.amountPaid).toBe(-15.7);
|
||||
expect(order2.appliedRounding).toBe(0.02);
|
||||
expect(order2.change).toBe(0.0);
|
||||
});
|
||||
|
||||
test("[Old Tour] test_cash_rounding_halfup_add_invoice_line_not_only_round_cash_method_pay_by_bank_and_cash", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", false);
|
||||
const product = prepareProduct(store);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
|
||||
expect(order.displayPrice).toBe(15.72);
|
||||
expect(order.priceExcl).toBe(13.67);
|
||||
expect(order.totalDue).toBe(15.7);
|
||||
order.addPaymentline(cardPm);
|
||||
order.payment_ids[0].setAmount(0.68);
|
||||
expect(order.amountPaid).toBe(0.68);
|
||||
expect(order.remainingDue).toBe(15.02); // Order is rounded globaly so remaining due is rounded
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[1].amount).toBe(15.02);
|
||||
expect(order.amountPaid).toBe(15.7);
|
||||
expect(order.appliedRounding).toBe(-0.02);
|
||||
expect(order.change).toBe(0.0);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
order2.is_refund = true;
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: -1 }, order2);
|
||||
expect(order2.displayPrice).toBe(-15.72);
|
||||
expect(order2.priceExcl).toBe(-13.67);
|
||||
expect(order2.totalDue).toBe(-15.7);
|
||||
order2.addPaymentline(cardPm);
|
||||
order2.payment_ids[0].setAmount(-0.68);
|
||||
expect(order2.amountPaid).toBe(-0.68);
|
||||
expect(order2.remainingDue).toBe(-15.02); // Order is rounded globaly so remaining due is rounded
|
||||
order2.addPaymentline(cashPm);
|
||||
expect(order2.payment_ids[1].amount).toBe(-15.02);
|
||||
expect(order2.amountPaid).toBe(-15.7);
|
||||
expect(order2.appliedRounding).toBe(0.02);
|
||||
expect(order2.change).toBe(0.0);
|
||||
});
|
||||
|
||||
test("[Old Tour] test_cash_rounding_down_add_invoice_line_not_only_round_cash_method_no_rounding_left", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", false);
|
||||
const product = prepareProduct(store);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
|
||||
expect(order.displayPrice).toBe(15.72);
|
||||
expect(order.priceExcl).toBe(13.67);
|
||||
expect(order.totalDue).toBe(15.7);
|
||||
order.addPaymentline(cardPm);
|
||||
order.payment_ids[0].setAmount(0.67);
|
||||
expect(order.amountPaid).toBe(0.67);
|
||||
expect(order.remainingDue).toBe(15.03);
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[1].amount).toBe(15.03);
|
||||
expect(order.amountPaid).toBe(15.7);
|
||||
expect(order.appliedRounding).toBe(-0.02);
|
||||
expect(order.change).toBe(0.0);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
order2.is_refund = true;
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: -1 }, order2);
|
||||
expect(order2.displayPrice).toBe(-15.72);
|
||||
expect(order2.priceExcl).toBe(-13.67);
|
||||
expect(order2.totalDue).toBe(-15.7);
|
||||
order2.addPaymentline(cardPm);
|
||||
order2.payment_ids[0].setAmount(-0.67);
|
||||
expect(order2.amountPaid).toBe(-0.67);
|
||||
expect(order2.remainingDue).toBe(-15.03);
|
||||
order2.addPaymentline(cardPm);
|
||||
expect(order2.payment_ids[1].amount).toBe(-15.03);
|
||||
expect(order2.amountPaid).toBe(-15.7);
|
||||
expect(order2.appliedRounding).toBe(0.02);
|
||||
expect(order2.change).toBe(0.0);
|
||||
});
|
||||
|
||||
test("[Old Tour] test_cash_rounding_halfup_add_invoice_line_only_round_cash_method", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm } = prepareRoundingVals(store, 0.05, "HALF-UP", true);
|
||||
const product = prepareProduct(store);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
|
||||
expect(order.displayPrice).toBe(15.72);
|
||||
expect(order.priceExcl).toBe(13.67);
|
||||
expect(order.totalDue).toBe(15.72);
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.amountPaid).toBe(15.7);
|
||||
expect(order.appliedRounding).toBe(-0.02);
|
||||
expect(order.change).toBe(0.0);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
order2.is_refund = true;
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: -1 }, order2);
|
||||
expect(order2.displayPrice).toBe(-15.72);
|
||||
expect(order2.priceExcl).toBe(-13.67);
|
||||
expect(order2.totalDue).toBe(-15.72);
|
||||
order2.addPaymentline(cashPm);
|
||||
expect(order2.amountPaid).toBe(-15.7);
|
||||
expect(order2.appliedRounding).toBe(0.02);
|
||||
expect(order2.change).toBe(0.0);
|
||||
});
|
||||
|
||||
test("[Old Tour] test_cash_rounding_halfup_add_invoice_line_only_round_cash_method_pay_by_bank_and_cash", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", true);
|
||||
const product = prepareProduct(store);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
|
||||
expect(order.displayPrice).toBe(15.72);
|
||||
expect(order.priceExcl).toBe(13.67);
|
||||
expect(order.totalDue).toBe(15.72);
|
||||
order.addPaymentline(cardPm);
|
||||
order.payment_ids[0].setAmount(0.68);
|
||||
expect(order.amountPaid).toBe(0.68);
|
||||
expect(order.remainingDue).toBe(15.04);
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[1].amount).toBe(15.05);
|
||||
expect(order.amountPaid).toBe(15.73);
|
||||
expect(order.appliedRounding).toBe(0.01);
|
||||
expect(order.change).toBe(0.0);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
order2.is_refund = true;
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: -1 }, order2);
|
||||
expect(order2.displayPrice).toBe(-15.72);
|
||||
expect(order2.priceExcl).toBe(-13.67);
|
||||
expect(order2.totalDue).toBe(-15.72);
|
||||
order2.addPaymentline(cardPm);
|
||||
order2.payment_ids[0].setAmount(-0.68);
|
||||
expect(order2.amountPaid).toBe(-0.68);
|
||||
expect(order2.remainingDue).toBe(-15.04);
|
||||
order2.addPaymentline(cashPm);
|
||||
expect(order2.payment_ids[1].amount).toBe(-15.05);
|
||||
expect(order2.amountPaid).toBe(-15.73);
|
||||
expect(order2.appliedRounding).toBe(-0.01);
|
||||
expect(order2.change).toBe(0.0);
|
||||
});
|
||||
|
||||
test("[Old Tour] test_cash_rounding_with_change", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", false);
|
||||
const product = prepareProduct(store);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
|
||||
expect(order.displayPrice).toBe(15.72);
|
||||
expect(order.priceExcl).toBe(13.67);
|
||||
expect(order.totalDue).toBe(15.7);
|
||||
order.addPaymentline(cardPm);
|
||||
order.payment_ids[0].setAmount(20);
|
||||
expect(order.amountPaid).toBe(20);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(-4.3);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
order2.is_refund = true;
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: -1 }, order2);
|
||||
expect(order2.displayPrice).toBe(-15.72);
|
||||
expect(order2.priceExcl).toBe(-13.67);
|
||||
expect(order2.totalDue).toBe(-15.7);
|
||||
order2.addPaymentline(cardPm);
|
||||
order2.payment_ids[0].setAmount(-20);
|
||||
expect(order2.amountPaid).toBe(-20);
|
||||
expect(order2.appliedRounding).toBe(0);
|
||||
expect(order2.change).toBe(4.3);
|
||||
});
|
||||
|
||||
test("[Old Tour] test_cash_rounding_only_cash_method_with_change", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm } = prepareRoundingVals(store, 0.05, "HALF-UP", true);
|
||||
const product = prepareProduct(store);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: 1 }, order);
|
||||
expect(order.displayPrice).toBe(15.72);
|
||||
expect(order.priceExcl).toBe(13.67);
|
||||
expect(order.totalDue).toBe(15.72);
|
||||
order.addPaymentline(cashPm);
|
||||
order.payment_ids[0].setAmount(20);
|
||||
expect(order.amountPaid).toBe(20);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(-4.3);
|
||||
|
||||
const order2 = store.addNewOrder();
|
||||
order2.pricelist_id = false;
|
||||
order2.is_refund = true;
|
||||
await store.addLineToOrder({ product_tmpl_id: product, qty: -1 }, order2);
|
||||
expect(order2.displayPrice).toBe(-15.72);
|
||||
expect(order2.priceExcl).toBe(-13.67);
|
||||
expect(order2.totalDue).toBe(-15.72);
|
||||
order2.addPaymentline(cashPm);
|
||||
order2.payment_ids[0].setAmount(-20);
|
||||
expect(order2.amountPaid).toBe(-20);
|
||||
expect(order2.appliedRounding).toBe(0);
|
||||
expect(order2.change).toBe(4.3);
|
||||
});
|
||||
|
||||
test(["[Old Tour] test_cash_rounding_up_with_change"], async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm } = prepareRoundingVals(store, 1, "UP", true);
|
||||
const order = store.addNewOrder();
|
||||
order.pricelist_id = false;
|
||||
|
||||
const tax = store.models["account.tax"].get(3);
|
||||
const productA = store.models["product.template"].get(15);
|
||||
const productB = store.models["product.template"].get(16);
|
||||
productA.list_price = 95;
|
||||
productA.product_variant_ids[0].lst_price = 95;
|
||||
productA.taxes_id = [tax];
|
||||
productB.list_price = 42;
|
||||
productB.product_variant_ids[0].lst_price = 42;
|
||||
productB.taxes_id = [tax];
|
||||
|
||||
await store.addLineToOrder({ product_tmpl_id: productA, qty: 1 }, order);
|
||||
await store.addLineToOrder({ product_tmpl_id: productB, qty: 2 }, order);
|
||||
|
||||
expect(order.displayPrice).toBe(179);
|
||||
expect(order.totalDue).toBe(179);
|
||||
order.addPaymentline(cashPm);
|
||||
order.payment_ids[0].setAmount(200);
|
||||
expect(order.amountPaid).toBe(200);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(-21);
|
||||
});
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { expectFormattedPrice, setupPosEnv } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { getFilledOrderForPriceCheck } from "./utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("Prices includes", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
const details = order.prices.taxDetails;
|
||||
const line1 = order.lines[0].prices;
|
||||
const line2 = order.lines[1].prices;
|
||||
|
||||
// Order prices
|
||||
expect(details.base_amount).toBe(1100);
|
||||
expect(details.tax_amount).toBe(290);
|
||||
expect(details.total_amount).toBe(1390);
|
||||
|
||||
// First line (25% on 1000)
|
||||
expect(line1.total_included).toBe(1250);
|
||||
expect(line1.total_excluded).toBe(1000);
|
||||
expect(line1.taxes_data[0].tax_amount).toBe(250);
|
||||
expect(line1.taxes_data[0].tax.amount).toBe(25);
|
||||
|
||||
// Second line (15% + 25% on 100)
|
||||
expect(line2.total_included).toBe(140);
|
||||
expect(line2.total_excluded).toBe(100);
|
||||
expect(line2.taxes_data[0].tax_amount).toBe(15);
|
||||
expect(line2.taxes_data[0].tax.amount).toBe(15);
|
||||
expect(line2.taxes_data[1].tax_amount).toBe(25);
|
||||
expect(line2.taxes_data[1].tax.amount).toBe(25);
|
||||
|
||||
// Formatted prices
|
||||
expectFormattedPrice(order.currencyDisplayPrice, "$ 1,390.00");
|
||||
expectFormattedPrice(order.currencyAmountTaxes, "$ 290.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPrice, "$ 1,250.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPriceUnit, "$ 1,250.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPriceUnitExcl, "$ 1,000.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPrice, "$ 140.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPriceUnit, "$ 140.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPriceUnitExcl, "$ 100.00");
|
||||
});
|
||||
|
||||
test("Prices excludes", async () => {
|
||||
const store = await setupPosEnv();
|
||||
store.config.iface_tax_included = "subtotal";
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
// Formatted prices
|
||||
expectFormattedPrice(order.currencyDisplayPrice, "$ 1,100.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPrice, "$ 1,000.00");
|
||||
expectFormattedPrice(order.lines[0].currencyDisplayPriceUnit, "$ 1,000.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPrice, "$ 100.00");
|
||||
expectFormattedPrice(order.lines[1].currencyDisplayPriceUnit, "$ 100.00");
|
||||
});
|
||||
|
||||
test("Combo prices incl and excl", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const template = store.models["product.template"].get(7);
|
||||
const comboProduct = store.models["product.combo.item"].get(1);
|
||||
|
||||
await store.addLineToOrder(
|
||||
{
|
||||
product_tmpl_id: template,
|
||||
payload: [[{ combo_item_id: comboProduct, qty: 1 }]],
|
||||
qty: 1,
|
||||
},
|
||||
order
|
||||
);
|
||||
order.setOrderPrices();
|
||||
|
||||
const [comboParentLine, comboChildLine] = order.lines;
|
||||
|
||||
expect(comboParentLine.comboTotalPrice).toBe(3.75);
|
||||
expect(comboParentLine.comboTotalPriceWithoutTax).toBe(3);
|
||||
|
||||
expect(comboChildLine.comboTotalPrice).toBe(3.75);
|
||||
expect(comboChildLine.comboTotalPriceWithoutTax).toBe(3);
|
||||
});
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { setupPosEnv } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("Pricelist: Precedence Rules (Variant > Template > Category > Global)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const pricelist = store.models["product.pricelist"].create({
|
||||
name: "Test Pricelist",
|
||||
});
|
||||
|
||||
const category = store.models["product.category"].create({ name: "Test Category" });
|
||||
const productTemplate = store.models["product.template"].create({
|
||||
name: "Test Template",
|
||||
list_price: 100,
|
||||
categ_id: category,
|
||||
});
|
||||
const product = store.models["product.product"].create({
|
||||
product_tmpl_id: productTemplate,
|
||||
lst_price: 100,
|
||||
});
|
||||
|
||||
// 1. Global Rule
|
||||
const globalRule = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: pricelist,
|
||||
compute_price: "fixed",
|
||||
fixed_price: 90,
|
||||
});
|
||||
pricelist.update({ item_ids: [globalRule] });
|
||||
pricelist.computeRuleIndexes();
|
||||
expect(product.getPrice(pricelist, 1, 0, false, product)).toBe(90);
|
||||
|
||||
// 2. Category Rule (should win over Global)
|
||||
const categoryRule = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: pricelist,
|
||||
categ_id: category,
|
||||
compute_price: "fixed",
|
||||
fixed_price: 80,
|
||||
});
|
||||
pricelist.update({ item_ids: [globalRule, categoryRule] });
|
||||
pricelist.computeRuleIndexes();
|
||||
expect(product.getPrice(pricelist, 1, 0, false, product)).toBe(80);
|
||||
|
||||
// 3. Template Rule (should win over Category)
|
||||
const templateRule = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: pricelist,
|
||||
product_tmpl_id: productTemplate,
|
||||
compute_price: "fixed",
|
||||
fixed_price: 70,
|
||||
});
|
||||
pricelist.update({ item_ids: [globalRule, categoryRule, templateRule] });
|
||||
pricelist.computeRuleIndexes();
|
||||
expect(product.getPrice(pricelist, 1, 0, false, product)).toBe(70);
|
||||
|
||||
// 4. Variant Rule (should win over Template)
|
||||
const variantRule = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: pricelist,
|
||||
product_id: product,
|
||||
compute_price: "fixed",
|
||||
fixed_price: 60,
|
||||
});
|
||||
pricelist.update({ item_ids: [globalRule, categoryRule, templateRule, variantRule] });
|
||||
pricelist.computeRuleIndexes();
|
||||
expect(product.getPrice(pricelist, 1, 0, false, product)).toBe(60);
|
||||
});
|
||||
|
||||
test("Pricelist: Min Quantity logic", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const pricelist = store.models["product.pricelist"].create({
|
||||
name: "Qty Pricelist",
|
||||
});
|
||||
|
||||
const productTemplate = store.models["product.template"].create({
|
||||
name: "Qty Product",
|
||||
list_price: 100,
|
||||
});
|
||||
const product = store.models["product.product"].create({
|
||||
product_tmpl_id: productTemplate,
|
||||
lst_price: 100,
|
||||
});
|
||||
|
||||
const ruleSmall = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: pricelist,
|
||||
product_id: product,
|
||||
compute_price: "fixed",
|
||||
fixed_price: 50,
|
||||
min_quantity: 0,
|
||||
});
|
||||
const ruleLarge = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: pricelist,
|
||||
product_id: product,
|
||||
compute_price: "fixed",
|
||||
fixed_price: 20,
|
||||
min_quantity: 10,
|
||||
});
|
||||
|
||||
pricelist.update({ item_ids: [ruleSmall, ruleLarge] });
|
||||
pricelist.computeRuleIndexes();
|
||||
|
||||
// Qty 1 -> should use ruleSmall (50)
|
||||
expect(product.getPrice(pricelist, 1, 0, false, product)).toBe(50);
|
||||
// Qty 10 -> should use ruleLarge (20)
|
||||
expect(product.getPrice(pricelist, 10, 0, false, product)).toBe(20);
|
||||
// Qty 15 -> should use ruleLarge (20)
|
||||
expect(product.getPrice(pricelist, 15, 0, false, product)).toBe(20);
|
||||
});
|
||||
|
||||
test("Pricelist: Nested Pricelists (Pricelist of Pricelist)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
|
||||
// Base Pricelist: -10% discount
|
||||
const basePricelist = store.models["product.pricelist"].create({
|
||||
name: "Base Pricelist",
|
||||
});
|
||||
const baseRule = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: basePricelist,
|
||||
compute_price: "percentage",
|
||||
percent_price: 10,
|
||||
base: "list_price",
|
||||
});
|
||||
basePricelist.update({ item_ids: [baseRule] });
|
||||
basePricelist.computeRuleIndexes();
|
||||
|
||||
// Nested Pricelist: Base + another -5$ surcharge
|
||||
const nestedPricelist = store.models["product.pricelist"].create({
|
||||
name: "Nested Pricelist",
|
||||
});
|
||||
const nestedRule = store.models["product.pricelist.item"].create({
|
||||
pricelist_id: nestedPricelist,
|
||||
compute_price: "formula", // formula to use base + surcharge
|
||||
base: "pricelist",
|
||||
base_pricelist_id: basePricelist,
|
||||
price_surcharge: 5,
|
||||
});
|
||||
nestedPricelist.update({ item_ids: [nestedRule] });
|
||||
nestedPricelist.computeRuleIndexes();
|
||||
|
||||
const productTemplate = store.models["product.template"].create({
|
||||
name: "Nested Test Product",
|
||||
list_price: 100,
|
||||
});
|
||||
const product = store.models["product.product"].create({
|
||||
product_tmpl_id: productTemplate,
|
||||
lst_price: 100,
|
||||
});
|
||||
|
||||
// Calculation: (100 - 10%) + 5 = 90 + 5 = 95
|
||||
expect(product.getPrice(nestedPricelist, 1, 0, false, product)).toBe(95);
|
||||
});
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { setupPosEnv } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { getFilledOrderForPriceCheck, prepareRoundingVals } from "./utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("Rounding sale HALF-UP 0.05 (cash only)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", true);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52.54);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52.55);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0.01);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding sale HALF-UP 0.05 (all methods)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52.55);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0.01);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52.55);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0.01);
|
||||
expect(order.change).toBe(0);
|
||||
|
||||
order.payment_ids[0].delete();
|
||||
order.addPaymentline(cashPm);
|
||||
order.payment_ids[0].setAmount(52.5);
|
||||
expect(order.payment_ids[0].amount).toBe(52.5);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.remainingDue).toBe(0.05);
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
});
|
||||
|
||||
test("Rounding sale UP 10 (cash only)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 10, "UP", true);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52.54);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(60);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(7.46);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding sale UP 10 (all methods)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 10, "UP", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(60);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(7.46);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(60);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(7.46);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(60);
|
||||
order.payment_ids[0].setAmount(70);
|
||||
expect(order.payment_ids[0].amount).toBe(70);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(-10);
|
||||
});
|
||||
|
||||
test("Rounding sale DOWN 10 (all methods)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 10, "DOWN", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(50);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-2.54);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(50);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-2.54);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(50);
|
||||
order.payment_ids[0].setAmount(70);
|
||||
expect(order.payment_ids[0].amount).toBe(70);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(-20);
|
||||
});
|
||||
|
||||
test("Rounding sale DOWN 1 (cash only)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 1, "DOWN", true);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52.54);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-0.54);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding sale DOWN 1 (all methods)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 1, "DOWN", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-0.54);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(52);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-0.54);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding refund HALF-UP 0.05 (cash only)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", true);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
order.is_refund = true;
|
||||
order.lines.map((line) => line.setQuantity(-line.qty));
|
||||
|
||||
expect(order.displayPrice).toBe(-52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52.54);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52.55);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-0.01);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding refund HALF-UP 0.05 (all methods)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
order.is_refund = true;
|
||||
order.lines.map((line) => line.setQuantity(-line.qty));
|
||||
|
||||
expect(order.displayPrice).toBe(-52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52.55);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-0.01);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52.55);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-0.01);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding refund UP 10 (cash only)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 10, "UP", true);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
order.is_refund = true;
|
||||
order.lines.map((line) => line.setQuantity(-line.qty));
|
||||
|
||||
expect(order.displayPrice).toBe(-52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52.54);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-60);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-7.46);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding refund UP 10 (all methods)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 10, "UP", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
order.is_refund = true;
|
||||
order.lines.map((line) => line.setQuantity(-line.qty));
|
||||
|
||||
expect(order.displayPrice).toBe(-52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-60);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-7.46);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-60);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(-7.46);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding refund DOWN 1 (cash only)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 1, "DOWN", true);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
order.is_refund = true;
|
||||
order.lines.map((line) => line.setQuantity(-line.qty));
|
||||
|
||||
expect(order.displayPrice).toBe(-52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52.54);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0.54);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rounding refund DOWN 1 (all methods)", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 1, "DOWN", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
order.is_refund = true;
|
||||
order.lines.map((line) => line.setQuantity(-line.qty));
|
||||
|
||||
expect(order.displayPrice).toBe(-52.54);
|
||||
|
||||
order.addPaymentline(cardPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0.54);
|
||||
expect(order.change).toBe(0);
|
||||
order.payment_ids[0].delete();
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[0].amount).toBe(-52);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0.54);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
||||
test("Rouding sale HALF-UP 0.05 with two payment method", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const { cashPm, cardPm } = prepareRoundingVals(store, 0.05, "HALF-UP", false);
|
||||
const order = await getFilledOrderForPriceCheck(store);
|
||||
|
||||
expect(order.displayPrice).toBe(52.54);
|
||||
|
||||
// only_round_cash_method is false so the order due is 52.55
|
||||
order.addPaymentline(cardPm);
|
||||
order.payment_ids[0].setAmount(2.54);
|
||||
expect(order.payment_ids[0].amount).toBe(2.54);
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
expect(order.remainingDue).toBe(50.01);
|
||||
order.addPaymentline(cashPm);
|
||||
|
||||
// Cash rounding is not applied on the cash payment line but on the order due
|
||||
expect(order.payment_ids[1].amount).toBe(50.01);
|
||||
expect(order.remainingDue).toBe(0);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0.01);
|
||||
expect(order.change).toBe(0);
|
||||
|
||||
// Set only_round_cash_method to true and check that the order due is now 52.54
|
||||
order.config_id.only_round_cash_method = true;
|
||||
order.payment_ids = [];
|
||||
order.addPaymentline(cardPm);
|
||||
order.payment_ids[0].setAmount(2.54);
|
||||
expect(order.payment_ids[0].amount).toBe(2.54);
|
||||
expect(order.canBeValidated()).toBe(false);
|
||||
expect(order.remainingDue).toBe(50);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
order.addPaymentline(cashPm);
|
||||
expect(order.payment_ids[1].amount).toBe(50);
|
||||
expect(order.remainingDue).toBe(0);
|
||||
expect(order.canBeValidated()).toBe(true);
|
||||
expect(order.appliedRounding).toBe(0);
|
||||
expect(order.change).toBe(0);
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
const { DateTime } = luxon;
|
||||
|
||||
/**
|
||||
* We use a dedicated method for the price check because we don't want to use
|
||||
* getFilledOrder in case of modification of this method breaks the price test.
|
||||
*
|
||||
* This method is a copy of getFilledOrder from utils.js
|
||||
*/
|
||||
export const getFilledOrderForPriceCheck = async (store, data = {}) => {
|
||||
const order = store.addNewOrder(data);
|
||||
// This product1 has a 25% tax with a 100.0 price
|
||||
// This product2 has a 15% + 25% tax with a 1000.0 price
|
||||
const product1 = store.models["product.template"].get(15);
|
||||
const product2 = store.models["product.template"].get(16);
|
||||
|
||||
const date = DateTime.now();
|
||||
order.write_date = date;
|
||||
order.create_date = date;
|
||||
order.pricelist_id = false;
|
||||
|
||||
await store.addLineToOrder(
|
||||
{
|
||||
product_tmpl_id: product1,
|
||||
qty: 1,
|
||||
write_date: date,
|
||||
create_date: date,
|
||||
},
|
||||
order
|
||||
);
|
||||
await store.addLineToOrder(
|
||||
{
|
||||
product_tmpl_id: product2,
|
||||
qty: 1,
|
||||
write_date: date,
|
||||
create_date: date,
|
||||
},
|
||||
order
|
||||
);
|
||||
store.addPendingOrder([order.id]);
|
||||
return order;
|
||||
};
|
||||
|
||||
export const prepareRoundingVals = (store, roundingAmount, roundingMethod, onlyCash = true) => {
|
||||
const config = store.config;
|
||||
const product1 = store.models["product.template"].get(15);
|
||||
const product2 = store.models["product.template"].get(16);
|
||||
const cashPm = store.models["pos.payment.method"].find((pm) => pm.is_cash_count);
|
||||
const cardPm = store.models["pos.payment.method"].find((pm) => !pm.is_cash_count);
|
||||
|
||||
// Changes prices to have a non rounded change
|
||||
product1.list_price = 15.73;
|
||||
product2.list_price = 23.49;
|
||||
product1.product_variant_ids[0].lst_price = 15.73;
|
||||
product2.product_variant_ids[0].lst_price = 23.49;
|
||||
|
||||
config.cash_rounding = true;
|
||||
config.only_round_cash_method = onlyCash;
|
||||
config.rounding_method = store.models["account.cash.rounding"].create({
|
||||
name: "roudning",
|
||||
rounding: roundingAmount,
|
||||
rounding_method: roundingMethod,
|
||||
strategy: "add_invoice_line",
|
||||
});
|
||||
|
||||
return { config, cashPm, cardPm };
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { test, expect, animationFrame } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { OrderSummary } from "@point_of_sale/app/screens/product_screen/order_summary/order_summary";
|
||||
import { setupPosEnv, getFilledOrder } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { queryAll, queryOne } from "@odoo/hoot-dom";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("getNewLine", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrder(store);
|
||||
const orderSummary = await mountWithCleanup(OrderSummary, {});
|
||||
order.getSelectedOrderline().uiState.savedQuantity = 5;
|
||||
const newLine = orderSummary.getNewLine();
|
||||
expect(newLine.order_id.id).toBe(order.id);
|
||||
expect(newLine.qty).toBe(0);
|
||||
});
|
||||
|
||||
test("Display tax include/exclude subtotal label", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrder(store);
|
||||
|
||||
order.config.iface_tax_included = "total";
|
||||
await mountWithCleanup(OrderSummary, {});
|
||||
const total = queryOne(".total");
|
||||
const subtotal = queryAll(".subtotal");
|
||||
expect(subtotal).toHaveLength(0);
|
||||
expect(total.innerHTML).toBe("$ 17.85");
|
||||
|
||||
order.config.iface_tax_included = "subtotal";
|
||||
await animationFrame();
|
||||
const total2 = queryOne(".total");
|
||||
const subtotal2 = queryOne(".subtotal");
|
||||
expect(total2.innerHTML).toBe("$ 17.85");
|
||||
expect(subtotal2.innerHTML).toBe("$ 15.00");
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { Orderline } from "@point_of_sale/app/components/orderline/orderline";
|
||||
import { expectFormattedPrice, setupPosEnv } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("orderline.js", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = store.addNewOrder();
|
||||
const product = store.models["product.template"].get(5);
|
||||
const line = await store.addLineToOrder(
|
||||
{
|
||||
product_tmpl_id: product,
|
||||
qty: 3,
|
||||
note: '[{"text":"Test 1","colorIndex":0},{"text":"Test 2","colorIndex":0}]',
|
||||
},
|
||||
order
|
||||
);
|
||||
|
||||
const comp = await mountWithCleanup(Orderline, {
|
||||
props: { line },
|
||||
});
|
||||
const lineData = comp.lineScreenValues;
|
||||
expect(comp.line.id).toEqual(line.id);
|
||||
expectFormattedPrice(comp.line.currencyDisplayPrice, "$ 10.35");
|
||||
expect(lineData.internalNote).toEqual([
|
||||
{
|
||||
text: "Test 1",
|
||||
colorIndex: 0,
|
||||
},
|
||||
{
|
||||
text: "Test 2",
|
||||
colorIndex: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { setupPosEnv, dialogActions } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { click } from "@odoo/hoot-dom";
|
||||
import { InternalNoteButton } from "@point_of_sale/app/screens/product_screen/control_buttons/orderline_note_button/orderline_note_button";
|
||||
import { OrderSummary } from "@point_of_sale/app/screens/product_screen/order_summary/order_summary";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("orderline_note_button.js", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = store.addNewOrder();
|
||||
const productTmplCombo = store.models["product.template"].get(7);
|
||||
|
||||
const productComboSteps = [
|
||||
() => click("#article_product_8"), // Wood Chair 1/2
|
||||
() => click("#article_product_8"), // Wood Chair 2/2
|
||||
() => click("#article_product_10"), // Wood desk
|
||||
() => click(".confirm"), // Confirm combo configuration
|
||||
];
|
||||
const lineAction = async () =>
|
||||
await store.addLineToCurrentOrder({
|
||||
product_tmpl_id: productTmplCombo,
|
||||
qty: 1,
|
||||
});
|
||||
const line = await dialogActions(lineAction, productComboSteps);
|
||||
expect(order.lines[0].qty).toBe(1);
|
||||
expect(order.lines[1].qty).toBe(1);
|
||||
expect(order.lines[2].qty).toBe(2);
|
||||
const orderSummary = await mountWithCleanup(OrderSummary, { props: {} });
|
||||
orderSummary._setValue(4);
|
||||
expect(order.lines[0].qty).toBe(4);
|
||||
expect(order.lines[1].qty).toBe(4);
|
||||
expect(order.lines[2].qty).toBe(8);
|
||||
const comp = await mountWithCleanup(InternalNoteButton, { props: { label: "" } });
|
||||
await comp.setChanges(line, '[{"1":"Test","colorIndex":0}]');
|
||||
order.updateLastOrderChange();
|
||||
orderSummary._setValue(9);
|
||||
|
||||
const noteAction = async () => await comp.setChanges(line, '[{"2":"Test","colorIndex":0}]');
|
||||
await dialogActions(noteAction, productComboSteps);
|
||||
// Check quantity
|
||||
expect(order.lines[0].qty).toBe(4);
|
||||
expect(order.lines[1].qty).toBe(4);
|
||||
expect(order.lines[2].qty).toBe(8);
|
||||
expect(order.lines[3].qty).toBe(5);
|
||||
expect(order.lines[4].qty).toBe(5);
|
||||
expect(order.lines[5].qty).toBe(10);
|
||||
|
||||
// Check notes (only on parent lines)
|
||||
expect(order.lines[0].note).toBe('[{"1":"Test","colorIndex":0}]');
|
||||
expect(order.lines[3].note).toBe('[{"2":"Test","colorIndex":0}]');
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { test, animationFrame } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { setupPosEnv, getFilledOrder, expectFormattedPrice } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { queryOne } from "@odoo/hoot-dom";
|
||||
import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("Change always incl", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrder(store);
|
||||
const firstPm = store.models["pos.payment.method"].getFirst();
|
||||
order.config.iface_tax_included = "total";
|
||||
const comp = await mountWithCleanup(PaymentScreen, {
|
||||
props: { orderUuid: order.uuid },
|
||||
});
|
||||
await comp.addNewPaymentLine(firstPm);
|
||||
order.payment_ids[0].setAmount(20);
|
||||
await animationFrame();
|
||||
const total = queryOne(".amount");
|
||||
expectFormattedPrice(total.attributes.amount.value, "$ -2.15");
|
||||
order.config.iface_tax_included = "subtotal";
|
||||
await animationFrame();
|
||||
const subtotal = queryOne(".amount");
|
||||
expectFormattedPrice(subtotal.attributes.amount.value, "$ -2.15");
|
||||
});
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { setupPosEnv } from "../utils";
|
||||
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("_getProductByBarcode", async () => {
|
||||
const store = await setupPosEnv();
|
||||
store.addNewOrder();
|
||||
const order = store.getOrder();
|
||||
const comp = await mountWithCleanup(ProductScreen, { props: { orderUuid: order.uuid } });
|
||||
await comp.addProductToOrder(store.models["product.template"].get(5));
|
||||
|
||||
expect(order.displayPrice).toBe(3.45);
|
||||
expect(comp.total).toBe("$\u00a03.45");
|
||||
expect(comp.items).toBe("1");
|
||||
|
||||
const productByBarcode = await comp._getProductByBarcode({ base_code: "test_test" });
|
||||
expect(productByBarcode.id).toEqual(5);
|
||||
});
|
||||
|
||||
test("fastValidate", async () => {
|
||||
const store = await setupPosEnv();
|
||||
store.addNewOrder();
|
||||
const order = store.getOrder();
|
||||
const fastPaymentMethod = order.config.fast_payment_method_ids[0];
|
||||
const productScreen = await mountWithCleanup(ProductScreen, {
|
||||
props: { orderUuid: order.uuid },
|
||||
});
|
||||
await productScreen.addProductToOrder(store.models["product.template"].get(5));
|
||||
|
||||
expect(order.displayPrice).toBe(3.45);
|
||||
expect(productScreen.total).toBe("$\u00a03.45");
|
||||
expect(productScreen.items).toBe("1");
|
||||
|
||||
await productScreen.fastValidate(fastPaymentMethod);
|
||||
|
||||
expect(order.payment_ids[0].payment_method_id).toEqual(fastPaymentMethod);
|
||||
expect(order.state).toBe("paid");
|
||||
expect(order.amount_paid).toBe(3.45);
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { setupPosEnv, getFilledOrder } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { queryOne } from "@odoo/hoot-dom";
|
||||
import { ReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/receipt_screen";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("Total on receipt always incl", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrder(store);
|
||||
order.config.iface_tax_included = "total";
|
||||
await mountWithCleanup(ReceiptScreen, {
|
||||
props: { orderUuid: order.uuid },
|
||||
});
|
||||
const total = queryOne(".pos-receipt-amount .pos-receipt-right-align");
|
||||
expect(total.innerHTML).toBe("$ 17.85");
|
||||
order.config.iface_tax_included = "subtotal";
|
||||
const subtotal = queryOne(".pos-receipt-amount .pos-receipt-right-align");
|
||||
expect(subtotal.innerHTML).toBe("$ 17.85");
|
||||
});
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { getFilledOrder, setupPosEnv, expectFormattedPrice } from "../utils";
|
||||
import { definePosModels } from "../data/generate_model_definitions";
|
||||
import { CustomerDisplayPosAdapter } from "@point_of_sale/app/customer_display/customer_display_adapter";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("getOrderlineData", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrder(store);
|
||||
|
||||
const adapter = new CustomerDisplayPosAdapter();
|
||||
adapter.formatOrderData(order);
|
||||
|
||||
expect(adapter.data.lines).toHaveLength(2);
|
||||
expect(adapter.data.lines[0].isSelected).toBe(false);
|
||||
expect(adapter.data.lines[1].isSelected).toBe(true);
|
||||
});
|
||||
|
||||
test("order amounts summary", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = await getFilledOrder(store);
|
||||
|
||||
const adapter = new CustomerDisplayPosAdapter();
|
||||
|
||||
adapter.formatOrderData(order);
|
||||
expectFormattedPrice(adapter.data.amount, "$ 17.85");
|
||||
expectFormattedPrice(adapter.data.amountTaxes, "$ 2.85");
|
||||
expect(adapter.data.subtotal).toBe(false);
|
||||
|
||||
store.config.iface_tax_included = "subtotal";
|
||||
adapter.formatOrderData(order);
|
||||
expectFormattedPrice(adapter.data.amount, "$ 17.85");
|
||||
expectFormattedPrice(adapter.data.amountTaxes, "$ 2.85");
|
||||
expectFormattedPrice(adapter.data.subtotal, "$ 15.00");
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountCashRounding extends models.ServerModel {
|
||||
_name = "account.cash.rounding";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountFiscalPosition extends models.ServerModel {
|
||||
_name = "account.fiscal.position";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return ["id", "name", "display_name", "tax_map", "tax_ids"];
|
||||
}
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Domestic",
|
||||
display_name: "Domestic",
|
||||
tax_ids: [1],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "No tax fp",
|
||||
display_name: "No tax fp",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountJournal extends models.ServerModel {
|
||||
_name = "account.journal";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return [];
|
||||
}
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Point of Sale",
|
||||
type: "sale",
|
||||
code: "POS",
|
||||
company_id: 250,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountMove extends models.ServerModel {
|
||||
_name = "account.move";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return ["id"];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountTax extends models.ServerModel {
|
||||
_name = "account.tax";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return [
|
||||
"id",
|
||||
"name",
|
||||
"price_include",
|
||||
"include_base_amount",
|
||||
"is_base_affected",
|
||||
"has_negative_factor",
|
||||
"amount_type",
|
||||
"children_tax_ids",
|
||||
"amount",
|
||||
"company_id",
|
||||
"id",
|
||||
"sequence",
|
||||
"tax_group_id",
|
||||
];
|
||||
}
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
name: "15%",
|
||||
price_include: false,
|
||||
include_base_amount: false,
|
||||
is_base_affected: true,
|
||||
has_negative_factor: false,
|
||||
amount_type: "percent",
|
||||
children_tax_ids: [],
|
||||
amount: 15.0,
|
||||
company_id: 250,
|
||||
sequence: 1,
|
||||
tax_group_id: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "25%",
|
||||
price_include: false,
|
||||
include_base_amount: false,
|
||||
is_base_affected: true,
|
||||
has_negative_factor: false,
|
||||
amount_type: "percent",
|
||||
children_tax_ids: [],
|
||||
amount: 25.0,
|
||||
company_id: 250,
|
||||
sequence: 1,
|
||||
tax_group_id: 3,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "tax incl",
|
||||
type_tax_use: "sale",
|
||||
amount_type: "percent",
|
||||
amount: 7,
|
||||
price_include_override: "tax_included",
|
||||
include_base_amount: true,
|
||||
has_negative_factor: true,
|
||||
company_id: 250,
|
||||
is_base_affected: true,
|
||||
tax_group_id: 4,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "15% incl",
|
||||
type_tax_use: "sale",
|
||||
amount_type: "percent",
|
||||
amount: 15,
|
||||
price_include_override: "tax_included",
|
||||
price_include: true,
|
||||
include_base_amount: true,
|
||||
has_negative_factor: false,
|
||||
company_id: 250,
|
||||
is_base_affected: true,
|
||||
tax_group_id: 5,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountTaxGroup extends models.ServerModel {
|
||||
_name = "account.tax.group";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return ["id", "name", "pos_receipt_label"];
|
||||
}
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tax 15%",
|
||||
pos_receipt_label: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Tax 0%",
|
||||
pos_receipt_label: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Tax 25%",
|
||||
pos_receipt_label: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "No group",
|
||||
pos_receipt_label: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "15% incl",
|
||||
pos_receipt_label: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class BarcodeNomenclature extends models.ServerModel {
|
||||
_name = "barcode.nomenclature";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class DecimalPrecision extends models.ServerModel {
|
||||
_name = "decimal.precision";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return ["id", "name", "digits"];
|
||||
}
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Product Unit",
|
||||
digits: 2,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Percentage Analytic",
|
||||
digits: 2,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Product Price",
|
||||
digits: 2,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Discount",
|
||||
digits: 2,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Stock Weight",
|
||||
digits: 2,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Volume",
|
||||
digits: 2,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Payment Terms",
|
||||
digits: 6,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import { defineModels, onRpc } from "@web/../tests/web_test_helpers";
|
||||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { ResourceCalendar } from "./resource_calendar.data";
|
||||
import { AccountMove } from "./account_move.data";
|
||||
import { PosSession } from "./pos_session.data";
|
||||
import { PosConfig } from "./pos_config.data";
|
||||
import { PosPreset } from "./pos_preset.data";
|
||||
import { ResourceCalendarAttendance } from "./resource_calendar_attendance";
|
||||
import { PosOrder } from "./pos_order.data";
|
||||
import { PosOrderLine } from "./pos_order_line.data";
|
||||
import { PosPackOperationLot } from "./pos_pack_operation_lot.data";
|
||||
import { PosPayment } from "./pos_payment.data";
|
||||
import { PosPaymentMethod } from "./pos_payment_method.data";
|
||||
import { PosPrinter } from "./pos_printer.data";
|
||||
import { PosCategory } from "./pos_category.data";
|
||||
import { PosBill } from "./pos_bill.data";
|
||||
import { ResCompany } from "./res_company.data";
|
||||
import { AccountTax } from "./account_tax.data";
|
||||
import { AccountTaxGroup } from "./account_tax_group.data";
|
||||
import { ProductTemplate } from "./product_template.data";
|
||||
import { ProductProduct } from "./product_product.data";
|
||||
import { ProductAttribute } from "./product_attribute.data";
|
||||
import { ProductAttributeCustomValue } from "./product_attribute_custom_value.data";
|
||||
import { ProductTemplateAttributeLine } from "./product_template_attribute_line.data";
|
||||
import { ProductTemplateAttributeValue } from "./product_template_attribute_value.data";
|
||||
import { ProductTemplateAttributeExclusion } from "./product_template_attribute_exclusion.data";
|
||||
import { ProductCombo } from "./product_combo.data";
|
||||
import { ProductComboItem } from "./product_combo_item.data";
|
||||
import { ResUsers } from "./res_users.data";
|
||||
import { ResPartner } from "./res_partner.data";
|
||||
import { ProductUom } from "./product_uom.data";
|
||||
import { DecimalPrecision } from "./decimal_precision.data";
|
||||
import { UomUom } from "./uom_uom.data";
|
||||
import { ResCountry } from "./res_country.data";
|
||||
import { ResCountryState } from "./res_country_state.data";
|
||||
import { ResLang } from "./res_lang.data";
|
||||
import { ProductCategory } from "./product_category.data";
|
||||
import { ProductPricelist } from "./product_pricelist.data";
|
||||
import { ProductPricelistItem } from "./product_pricelist_item.data";
|
||||
import { AccountCashRounding } from "./account_cash_rounding.data";
|
||||
import { AccountFiscalPosition } from "./account_fiscal_position.data";
|
||||
import { StockPickingType } from "./stock_picking_type.data";
|
||||
import { ResCurrency } from "./res_currency.data";
|
||||
import { PosNote } from "./pos_note.data";
|
||||
import { ProductTag } from "./product_tag.data";
|
||||
import { IrModuleModule } from "./ir_module_module.data";
|
||||
import { AccountJournal } from "./account_journal.data";
|
||||
import { IrSequence } from "./ir_sequence.data";
|
||||
import { StockWarehouse } from "./stock_warehouse.data";
|
||||
import { StockRoute } from "./stock_route.data";
|
||||
import { BarcodeNomenclature } from "./barcode_nomenclature.data";
|
||||
import { ProductAttributeValue } from "./product_attribute_value.data";
|
||||
|
||||
export const hootPosModels = [
|
||||
ResCountry,
|
||||
ResCountryState,
|
||||
ResCurrency,
|
||||
ResCompany,
|
||||
ResPartner,
|
||||
ResUsers,
|
||||
ResLang,
|
||||
PosSession,
|
||||
PosConfig,
|
||||
PosPreset,
|
||||
ResourceCalendarAttendance,
|
||||
PosOrder,
|
||||
PosOrderLine,
|
||||
PosPackOperationLot,
|
||||
PosPayment,
|
||||
PosPaymentMethod,
|
||||
PosPrinter,
|
||||
PosCategory,
|
||||
PosBill,
|
||||
AccountTax,
|
||||
AccountTaxGroup,
|
||||
AccountMove,
|
||||
ProductCategory,
|
||||
ProductTemplate,
|
||||
ProductProduct,
|
||||
ProductAttribute,
|
||||
ProductAttributeValue,
|
||||
ProductAttributeCustomValue,
|
||||
ProductTemplateAttributeLine,
|
||||
ProductTemplateAttributeValue,
|
||||
ProductTemplateAttributeExclusion,
|
||||
ProductCombo,
|
||||
ProductComboItem,
|
||||
ProductUom,
|
||||
ProductTag,
|
||||
ProductPricelist,
|
||||
ProductPricelistItem,
|
||||
DecimalPrecision,
|
||||
StockWarehouse,
|
||||
StockRoute,
|
||||
UomUom,
|
||||
AccountCashRounding,
|
||||
AccountFiscalPosition,
|
||||
StockPickingType,
|
||||
IrSequence,
|
||||
PosNote,
|
||||
IrModuleModule,
|
||||
AccountJournal,
|
||||
ResourceCalendar,
|
||||
BarcodeNomenclature,
|
||||
];
|
||||
|
||||
export const definePosModels = () => {
|
||||
const posModelNames = hootPosModels.map((modelClass) => modelClass.prototype.constructor._name);
|
||||
const modelsFromMail = Object.values(mailModels).filter(
|
||||
(modelClass) => !posModelNames.includes(modelClass.prototype.constructor._name)
|
||||
);
|
||||
onRpc("/pos/ping", () => {});
|
||||
defineModels([...modelsFromMail, ...hootPosModels]);
|
||||
};
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { createRelatedModels } from "@point_of_sale/app/models/related_models";
|
||||
import { DataServiceOptions } from "@point_of_sale/app/models/data_service_options";
|
||||
import { MockServer } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export const getModelDefinitions = () => {
|
||||
const session = MockServer.current._models["pos.session"];
|
||||
const params = session.load_data_params();
|
||||
return Object.entries(params).reduce((acc, [modelName, params]) => {
|
||||
acc[modelName] = params.relations;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
let generatedModels = null;
|
||||
|
||||
export const getRelatedModelsInstance = (useModelClass = true) => {
|
||||
if (generatedModels) {
|
||||
return generatedModels;
|
||||
}
|
||||
|
||||
const options = new DataServiceOptions();
|
||||
const relations = getModelDefinitions();
|
||||
const modelClasses = {};
|
||||
|
||||
if (useModelClass) {
|
||||
for (const posModel of registry.category("pos_available_models").getAll()) {
|
||||
const pythonModel = posModel.pythonModel;
|
||||
const extraFields = posModel.extraFields || {};
|
||||
|
||||
modelClasses[pythonModel] = posModel;
|
||||
relations[pythonModel] = {
|
||||
...relations[pythonModel],
|
||||
...extraFields,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const models = createRelatedModels(relations, useModelClass ? modelClasses : {}, options);
|
||||
generatedModels = models.models;
|
||||
return models.models;
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class IrModuleModule extends models.ServerModel {
|
||||
_name = "ir.module.module";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return ["id", "name", "state"];
|
||||
}
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 901,
|
||||
name: "pos_settle_due",
|
||||
state: "installed",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class IrSequence extends models.ServerModel {
|
||||
_name = "ir.sequence";
|
||||
|
||||
_load_pos_data_fields() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue