19.0 vanilla

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

View file

@ -0,0 +1,66 @@
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 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 { registry } from "@web/core/registry";
registry.category("web_tour.tours").add("test_pos_global_discount_sell_and_refund", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.addOrderline("Desk Pad", "1", "3"),
Chrome.clickOrders(),
Order.hasLine({
withoutClass: ".selected",
run: "click",
productName: "Desk Pad",
quantity: "1",
}),
// Check that the draft order's order line is not selected and not causing issues while
// comparing the line to the discount line
{
content: "Manually trigger keyup event",
trigger: ".ticket-screen",
run: function () {
window.dispatchEvent(new KeyboardEvent("keyup", { key: "9" }));
},
},
TicketScreen.loadSelectedOrder(),
ProductScreen.clickControlButton("Discount"),
{
content: `click discount numpad button: 5`,
trigger: `.o_dialog div.numpad button:contains(5)`,
run: "click",
},
Dialog.confirm(),
ProductScreen.selectedOrderlineHas("discount", 1, "-0.15"),
ProductScreen.totalAmountIs("2.85"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
ReceiptScreen.clickNextOrder(),
...ProductScreen.clickRefund(),
TicketScreen.selectOrder("001"),
ProductScreen.clickNumpad("1"),
TicketScreen.toRefundTextContains("To Refund: 1"),
ProductScreen.clickLine("discount"),
ProductScreen.clickNumpad("1"),
Dialog.confirm(),
TicketScreen.confirmRefund(),
PaymentScreen.isShown(),
PaymentScreen.clickBack(),
ProductScreen.clickLine("discount"),
ProductScreen.clickNumpad("1"),
Dialog.is({ title: "price update not allowed" }),
Dialog.confirm(),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});

View file

@ -0,0 +1,353 @@
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 { escapeRegExp } from "@web/core/utils/strings";
import { registry } from "@web/core/registry";
export function addDocument(documentParams) {
const steps = [];
for (const values of documentParams) {
steps.push(...ProductScreen.addOrderline(values.product, values.quantity));
}
steps.push(...[ProductScreen.clickPartnerButton(), ProductScreen.clickCustomer("AAAAAA")]);
return steps;
}
export function clickDiscountNumpad(num) {
return {
content: `click discount numpad button: ${num}`,
trigger: `.o_dialog div.numpad button:contains(/^${escapeRegExp(num)}$/)`,
run: "click",
};
}
export function addDiscount(percentage) {
const steps = [ProductScreen.clickControlButton("Discount")];
for (const num of percentage.split("")) {
steps.push(clickDiscountNumpad(num));
}
steps.push({
trigger: `.popup-input:contains(/^${escapeRegExp(percentage)}$/)`,
run: "click",
});
steps.push(Dialog.confirm());
return steps;
}
export function payAndInvoice(totalAmount) {
return [
ProductScreen.clickPayButton(),
PaymentScreen.totalIs(totalAmount),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.remainingIs("0.0"),
PaymentScreen.clickInvoiceButton(),
PaymentScreen.clickValidate(),
ReceiptScreen.receiptAmountTotalIs(totalAmount),
ReceiptScreen.clickNextOrder(),
];
}
registry
.category("web_tour.tours")
.add("test_taxes_l10n_in_pos_global_discount_round_per_line_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_1_1", quantity: "1" },
{ product: "product_1_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("35.91"),
ProductScreen.checkTaxAmount("4.76"),
...payAndInvoice("35.91"),
...addDocument([
{ product: "product_1_1", quantity: "1" },
{ product: "product_1_2", quantity: "1" },
]),
...addDiscount("7"),
ProductScreen.checkTotalAmount("34.08"),
ProductScreen.checkTaxAmount("4.53"),
...payAndInvoice("34.08"),
...addDocument([
{ product: "product_1_1", quantity: "1" },
{ product: "product_1_2", quantity: "1" },
]),
...addDiscount("18"),
ProductScreen.checkTotalAmount("30.04"),
ProductScreen.checkTaxAmount("3.99"),
...payAndInvoice("30.04"),
// On refund, check if the global discount line is correctly prorated in the refund order
...ProductScreen.clickRefund(),
TicketScreen.filterIs("Paid"),
TicketScreen.selectOrder("001"),
ProductScreen.clickNumpad("1"),
TicketScreen.confirmRefund(),
PaymentScreen.totalIs("-17.95"), // -18.32 (product_1_1) + 0.37 (discount)
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_in_pos_global_discount_round_globally_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_2_1", quantity: "1" },
{ product: "product_2_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("35.94"),
ProductScreen.checkTaxAmount("4.79"),
...payAndInvoice("35.94"),
...addDocument([
{ product: "product_2_1", quantity: "1" },
{ product: "product_2_2", quantity: "1" },
]),
...addDiscount("7"),
ProductScreen.checkTotalAmount("34.10"),
ProductScreen.checkTaxAmount("4.56"),
...payAndInvoice("34.10"),
...addDocument([
{ product: "product_2_1", quantity: "1" },
{ product: "product_2_2", quantity: "1" },
]),
...addDiscount("18"),
ProductScreen.checkTotalAmount("30.07"),
ProductScreen.checkTaxAmount("4.02"),
...payAndInvoice("30.07"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_in_pos_global_discount_round_per_line_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_3_1", quantity: "1" },
{ product: "product_3_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("35.91"),
ProductScreen.checkTaxAmount("4.76"),
...payAndInvoice("35.91"),
...addDocument([
{ product: "product_3_1", quantity: "1" },
{ product: "product_3_2", quantity: "1" },
]),
...addDiscount("7"),
ProductScreen.checkTotalAmount("34.08"),
ProductScreen.checkTaxAmount("4.53"),
...payAndInvoice("34.08"),
...addDocument([
{ product: "product_3_1", quantity: "1" },
{ product: "product_3_2", quantity: "1" },
]),
...addDiscount("18"),
ProductScreen.checkTotalAmount("30.04"),
ProductScreen.checkTaxAmount("3.99"),
...payAndInvoice("30.04"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_in_pos_global_discount_round_globally_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_4_1", quantity: "1" },
{ product: "product_4_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("35.93"),
ProductScreen.checkTaxAmount("4.79"),
...payAndInvoice("35.93"),
...addDocument([
{ product: "product_4_1", quantity: "1" },
{ product: "product_4_2", quantity: "1" },
]),
...addDiscount("7"),
ProductScreen.checkTotalAmount("34.09"),
ProductScreen.checkTaxAmount("4.56"),
...payAndInvoice("34.09"),
...addDocument([
{ product: "product_4_1", quantity: "1" },
{ product: "product_4_2", quantity: "1" },
]),
...addDiscount("18"),
ProductScreen.checkTotalAmount("30.06"),
ProductScreen.checkTaxAmount("4.02"),
...payAndInvoice("30.06"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_global_discount_round_per_line_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_1_1", quantity: "1" },
{ product: "product_1_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("94.08"),
ProductScreen.checkTaxAmount("30.7"),
...payAndInvoice("94.08"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_global_discount_round_globally_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_2_1", quantity: "1" },
{ product: "product_2_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("94.08"),
ProductScreen.checkTaxAmount("30.71"),
...payAndInvoice("94.08"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_global_discount_round_per_line_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_3_1", quantity: "1" },
{ product: "product_3_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("94.08"),
ProductScreen.checkTaxAmount("30.7"),
...payAndInvoice("94.08"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_global_discount_round_globally_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_4_1", quantity: "1" },
{ product: "product_4_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("94.08"),
ProductScreen.checkTaxAmount("30.71"),
...payAndInvoice("94.08"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_global_discount_round_per_line_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_1_1", quantity: "1" },
{ product: "product_1_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("42.25"),
ProductScreen.checkTaxAmount("9.34"),
...payAndInvoice("42.25"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_global_discount_round_globally_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_2_1", quantity: "1" },
{ product: "product_2_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("42.24"),
ProductScreen.checkTaxAmount("9.33"),
...payAndInvoice("42.24"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_global_discount_round_per_line_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_3_1", quantity: "1" },
{ product: "product_3_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("42.25"),
ProductScreen.checkTaxAmount("9.34"),
...payAndInvoice("42.25"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_global_discount_round_globally_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDocument([
{ product: "product_4_1", quantity: "1" },
{ product: "product_4_2", quantity: "1" },
]),
...addDiscount("2"),
ProductScreen.checkTotalAmount("42.25"),
ProductScreen.checkTaxAmount("9.33"),
...payAndInvoice("42.25"),
].flat(),
});

View file

@ -0,0 +1,71 @@
import { animationFrame, expect, test } from "@odoo/hoot";
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
import { setupPosEnv, expectFormattedPrice } from "@point_of_sale/../tests/unit/utils";
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("getNumpadButtons", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const product1 = store.models["product.template"].get(5);
await store.addLineToOrder(
{
product_tmpl_id: product1,
qty: 1,
},
order
);
const productScreen = await mountWithCleanup(ProductScreen, {
props: { orderUuid: order.uuid },
});
await store.applyDiscount(10);
await animationFrame();
const receivedButtonsDisableStatue = productScreen
.getNumpadButtons()
.filter((button) => ["quantity", "discount"].includes(button.value))
.map((button) => button.disabled);
expect(Math.abs(order.discountLines[0].priceIncl).toString()).toBe(
(order.lines[0].priceIncl * 0.1).toPrecision(2)
);
expect(receivedButtonsDisableStatue).toEqual([true, true]);
await productScreen.addProductToOrder(product1);
// Animation frame doesn't work here since the debounced function used to recompute
// discount is using eventListener, so we use setTimeout instead.
setTimeout(() => {
expect(Math.abs(order.discountLines[0].priceIncl).toString()).toBe(
(order.lines[0].priceIncl * 0.1).toPrecision(2)
);
}, 100);
});
test("addProductToOrder reapplies the global discount", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const product = store.models["product.template"].get(5);
const productScreen = await mountWithCleanup(ProductScreen, {
props: { orderUuid: order.uuid },
});
await productScreen.addProductToOrder(product);
expectFormattedPrice(productScreen.total, "$ 3.45");
expect(order.priceIncl).toBe(3.45);
expect(order.priceExcl).toBe(3);
expect(order.amountTaxes).toBe(0.45);
await store.applyDiscount(10);
expectFormattedPrice(productScreen.total, "$ 3.10");
expect(order.priceIncl).toBe(3.1);
expect(order.priceExcl).toBe(2.7);
expect(order.amountTaxes).toBe(0.4);
await productScreen.addProductToOrder(product);
await animationFrame();
expectFormattedPrice(productScreen.total, "$ 6.21");
expect(order.priceIncl).toBeCloseTo(6.21, { margin: 1e-12 });
expect(order.priceExcl).toBe(5.4);
expect(order.amountTaxes).toBe(0.81);
});

View file

@ -0,0 +1,7 @@
import { PosConfig } from "@point_of_sale/../tests/unit/data/pos_config.data";
PosConfig._records = PosConfig._records.map((record) => ({
...record,
module_pos_discount: true,
discount_product_id: 151,
}));

View file

@ -0,0 +1,17 @@
import { ProductProduct } from "@point_of_sale/../tests/unit/data/product_product.data";
ProductProduct._records = [
...ProductProduct._records,
{
id: 151,
product_tmpl_id: 151,
lst_price: 1,
standard_price: 0,
display_name: "Discount",
product_tag_ids: [],
barcode: false,
default_code: false,
product_template_attribute_value_ids: [],
product_template_variant_value_ids: [],
},
];

View file

@ -0,0 +1,19 @@
import { ProductTemplate } from "@point_of_sale/../tests/unit/data/product_template.data";
ProductTemplate._records = [
...ProductTemplate._records,
{
id: 151,
name: "Discount",
display_name: "Discount",
list_price: 0,
standard_price: 0,
type: "consu",
service_tracking: "none",
pos_categ_ids: [1],
categ_id: false,
uom_id: 1,
available_in_pos: true,
active: true,
},
];

View file

@ -0,0 +1,39 @@
import { animationFrame, expect, test } from "@odoo/hoot";
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("isDiscountLine", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const product1 = store.models["product.template"].get(5);
await store.addLineToOrder(
{
product_tmpl_id: product1,
qty: 1,
},
order
);
await store.applyDiscount(10);
await animationFrame();
const orderline = order.getSelectedOrderline();
expect(Math.abs(orderline.price_subtotal_incl).toString()).toBe(
((order.amount_total + order.amount_tax) * 0.1).toPrecision(2)
);
expect(orderline.isDiscountLine).toBe(true);
});
test("Test taxes after fiscal position with discount product (should not change)", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
order.fiscal_position_id = store.models["account.fiscal.position"].get(1);
await store.applyDiscount(20);
await animationFrame();
const discountLine = order.discountLines[0];
const lineValues = discountLine.prepareBaseLineForTaxesComputationExtraValues();
const recomputedTaxes = order.fiscal_position_id.getTaxesAfterFiscalPosition(
discountLine.product_id.taxes_id
);
expect(recomputedTaxes).not.toBe(lineValues.tax_ids);
});

View file

@ -0,0 +1,52 @@
import { test, describe, expect } from "@odoo/hoot";
import { patchWithCleanup } from "@web/../tests/web_test_helpers";
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
describe("PoS Discount", () => {
test("changing fiscal positions reapplies the global discount", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const product = store.models["product.template"].get(5);
await store.addLineToOrder({ product_tmpl_id: product, qty: 10 }, order);
expect(order.priceIncl).toBe(34.5);
expect(order.priceExcl).toBe(30);
expect(order.amountTaxes).toBe(4.5);
await store.applyDiscount(10);
expect(order.priceIncl).toBe(31.05);
expect(order.priceExcl).toBe(27);
expect(order.amountTaxes).toBe(4.05);
let [productLine, discountLine] = order.lines;
expect(productLine.priceIncl).toBe(34.5);
expect(discountLine.priceIncl).toBe(-3.45);
let resolveReapplyDiscount = null;
const reapplyDiscountPromise = new Promise((resolve) => {
resolveReapplyDiscount = resolve;
});
patchWithCleanup(store, {
async debouncedDiscount() {
await super.applyDiscount(...arguments);
resolveReapplyDiscount();
},
});
const nonTaxFP = store.models["account.fiscal.position"].get(2);
order.fiscal_position_id = nonTaxFP;
await reapplyDiscountPromise;
expect(order.priceIncl).toBe(27);
expect(order.priceExcl).toBe(27);
expect(order.amountTaxes).toBe(0);
[productLine, discountLine] = order.lines;
expect(productLine.priceIncl).toBe(30);
expect(discountLine.priceIncl).toBe(-3);
});
});