19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,84 +0,0 @@
odoo.define('pos_sale.tour.ProductScreenTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
const { Do, Check, Execute } = require('point_of_sale.tour.ProductScreenTourMethods');
class DoExt extends Do {
clickQuotationButton() {
return [
{
content: 'click quotation button',
trigger: '.o_sale_order_button',
}
];
}
selectFirstOrder() {
return [
{
content: `select order`,
trigger: `.order-row .col.name:first`,
},
{
content: `click on select the order`,
trigger: `.selection-item:contains('Settle the order')`,
}
];
}
selectNthOrder(n) {
return [
{
content: `select order`,
trigger: `.order-list .order-row:nth-child(${n})`,
},
{
content: `click on select the order`,
trigger: `.selection-item:contains('Settle the order')`,
}
];
}
downPaymentFirstOrder() {
return [
{
content: `select order`,
trigger: `.order-row .col.name:first`,
},
{
content: `click on select the order`,
trigger: `.selection-item:contains('Apply a down payment')`,
},
{
content: `click on +10 button`,
trigger: `.mode-button.add:contains('+10')`,
},
{
content: `click on ok button`,
trigger: `.button.confirm`,
}
];
}
}
class CheckExt extends Check{
checkCustomerNotes(note) {
return [
{
content: `check customer notes`,
trigger: `.orderline-note:contains(${note})`,
}
];
}
checkOrdersListEmpty() {
return [
{
content: 'Check that the orders list is empty',
trigger: '.order-list:not(:has(.order-row))',
}
]
}
}
return createTourMethods('ProductScreen', DoExt, CheckExt, Execute);
});

View file

@ -1,19 +0,0 @@
odoo.define('pos_sale.tour.ReceiptScreenTourMethods', function (require) {
'use strict';
const { createTourMethods } = require('point_of_sale.tour.utils');
const { Do, Check, Execute } = require('point_of_sale.tour.ReceiptScreenTourMethods');
class CheckExt extends Check{
checkCustomerNotes(note) {
return [
{
content: `check customer notes`,
trigger: `.pos-receipt-customer-note:contains(${note})`,
}
];
}
}
return createTourMethods('ReceiptScreen', Do, CheckExt, Execute);
});

View file

@ -0,0 +1,662 @@
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_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 ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
import * as PosSale from "@pos_sale/../tests/tours/utils/pos_sale_utils";
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 * as Utils from "@point_of_sale/../tests/generic_helpers/utils";
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add("PosSettleOrder", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Pizza Chicken", 9),
ProductScreen.clickNumpad("Qty", "2"), // Change the quantity of the product to 2
ProductScreen.selectedOrderlineHas("Pizza Chicken", 2),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
Chrome.clickOrders(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrderIncompatiblePartner", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
// The second item in the list is the first sale.order.
PosSale.settleNthOrder(2),
ProductScreen.selectedOrderlineHas("product1", 1),
ProductScreen.totalAmountIs("10.00"),
// The first item in the list is the second sale.order.
// Selecting the 2nd sale.order should use a new order,
// therefore, the total amount will change.
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("product2", 1),
ProductScreen.totalAmountIs("11.00"),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrder2", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.clickOrderline("Product A", "1"),
ProductScreen.selectedOrderlineHas("Product A", "1"),
ProductScreen.clickOrderline("Product B", "1"),
ProductScreen.clickNumpad("Qty", "0"),
ProductScreen.selectedOrderlineHas("Product B", "0"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.0" }),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PosRefundDownpayment", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.downPaymentFirstOrder("+10"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
...ProductScreen.clickRefund(),
// Filter should be automatically 'Paid'.
TicketScreen.filterIs("Paid"),
TicketScreen.selectOrder("001"),
Order.hasLine({
productName: "Down Payment",
withClass: ".selected",
quantity: "1",
}),
ProductScreen.clickNumpad("1"),
TicketScreen.confirmRefund(),
PaymentScreen.isShown(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrderRealTime", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.totalAmountIs(40),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrder3", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Product A", "1"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank", true, { remaining: "0.0" }),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrderNotGroupable", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.totalAmountIs(28.98), // 3.5 * 8 * 1.15 * 90%
ProductScreen.selectedOrderlineHas("Product A", "0.5"),
ProductScreen.checkOrderlinesNumber(4),
ProductScreen.selectedOrderlineHas("Product A", "0.5", "4.14"),
].flat(),
});
registry.category("web_tour.tours").add("test_import_lot_groupable_and_non_groupable", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1, { loadSN: true }),
PosSale.selectedOrderLinesHasLots("Groupable Product", []),
ProductScreen.checkOrderlinesNumber(5),
ProductScreen.totalAmountIs(60),
ProductScreen.selectedOrderlineHas("Groupable Product", "1", "10"),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrderWithNote", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
Order.hasLine({
customerNote: "Customer note 2--Customer note 3",
}),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
// Check in the receipt
Order.hasLine({
customerNote: "Customer note 2--Customer note 3",
}),
ReceiptScreen.clickNextOrder(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleAndInvoiceOrder", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
Order.hasLine({}),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickInvoiceButton(),
PaymentScreen.clickValidate(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleAndInvoiceOrder2", {
steps: () =>
[
Chrome.startPoS(),
PosSale.settleNthOrder(1),
Order.hasLine({}),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickInvoiceButton(),
PaymentScreen.clickValidate(),
].flat(),
});
registry.category("web_tour.tours").add("PosOrderDoesNotRemainInList", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
PosSale.checkOrdersListEmpty(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleDraftOrder", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Test service product", "1", "50.00"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleCustomPrice", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Product A", "1", "100"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A Test Partner AAA"),
ProductScreen.selectedOrderlineHas("Product A", "1", "100"),
].flat(),
});
registry.category("web_tour.tours").add("PoSSaleOrderWithDownpayment", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Down Payment (POS)"),
ProductScreen.totalAmountIs(980.0),
].flat(),
});
registry.category("web_tour.tours").add("test_settle_so_with_non_pos_groupable_uom", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Pomme de Terre", "0.5", "5.00"),
].flat(),
});
registry.category("web_tour.tours").add("PoSDownPaymentLinesPerTax", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.downPaymentFirstOrder("+20"),
Order.hasLine({
productName: "Down Payment",
quantity: "1",
price: "2.20",
}),
Order.hasLine({
productName: "Down Payment",
quantity: "1",
price: "1.00",
}),
Order.hasLine({
productName: "Down Payment",
quantity: "1",
price: "3.00",
}),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickInvoiceButton(),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PoSApplyDownpayment", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.downPaymentFirstOrder("+10"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
].flat(),
});
registry.category("web_tour.tours").add("PoSApplyDownpaymentInvoice", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.downPaymentFirstOrder("+10"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickInvoiceButton(),
PaymentScreen.clickValidate(),
].flat(),
});
registry.category("web_tour.tours").add("PoSApplyDownpaymentInvoice2", {
steps: () =>
[
Chrome.startPoS(),
PosSale.downPaymentFirstOrder("+10"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickInvoiceButton(),
PaymentScreen.clickValidate(),
].flat(),
});
registry.category("web_tour.tours").add("PosShipLaterNoDefault", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.clickPayButton(),
PaymentScreen.isShown(),
Utils.negateStep(PaymentScreen.shippingLaterHighlighted()),
].flat(),
});
registry.category("web_tour.tours").add("PosSaleTeam", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.clickDisplayedProduct("Test Product"),
ProductScreen.totalAmountIs("100.00"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
].flat(),
});
registry.category("web_tour.tours").add("PosOrdersListDifferentCurrency", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.clickControlButton("Quotation/Order"),
{
content: "Check that no orders are displayed",
trigger: '.o_nocontent_help p:contains("No record found")',
},
].flat(),
});
registry.category("web_tour.tours").add("PoSDownPaymentAmount", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.downPaymentFirstOrder("+20"),
Order.hasLine({
productName: "Down Payment",
quantity: "1",
price: "20.0",
}),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Cash"),
PaymentScreen.clickValidate(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrder4", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Product A", "1"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.remainingIs("0.0"),
PaymentScreen.clickShipLaterButton(),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrderShipLater", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(2),
ProductScreen.clickPayButton(),
PaymentScreen.clickShipLaterButton(),
PaymentScreen.shippingLaterHighlighted(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.remainingIs("0.0"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
ReceiptScreen.clickNextOrder(),
PosSale.settleNthOrder(1),
ProductScreen.clickPayButton(),
PaymentScreen.clickShipLaterButton(),
PaymentScreen.shippingLaterHighlighted(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.remainingIs("0.0"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PosSettleOrder5", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Product A", 1),
].flat(),
});
registry.category("web_tour.tours").add("PosSaleWarning", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A Test Customer 2"),
{
content: "Check warning popup are displayed",
trigger:
'.modal-dialog:has(.modal-header:contains("Warning for A Test Customer 2")):has(.modal-body:contains("Cannot afford our services"))',
},
{
trigger: ".modal-footer button",
run: "click",
},
ProductScreen.customerIsSelected("A Test Customer 2"),
ProductScreen.clickDisplayedProduct("Letter Tray", true, "1"),
ProductScreen.selectedOrderlineHas("Letter Tray", "1"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A Test Customer 1"),
{
content: "Check warning popup are displayed",
trigger:
'.modal-dialog:has(.modal-header:contains("Warning for A Test Customer 1")):has(.modal-body:contains("Highly infectious disease"))',
},
{
trigger: ".modal-footer button",
run: "click",
},
ProductScreen.customerIsSelected("A Test Customer 1"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.remainingIs("0.0"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("PoSSettleQuotation", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("POSSalePaymentScreenInvoiceOrder", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.addOrderline("Product Test", "1"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("AAA - Test Partner invoice"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickInvoiceButton(),
PaymentScreen.clickValidate(),
ReceiptScreen.receiptIsThere(),
Chrome.waitRequest(),
].flat(),
});
registry.category("web_tour.tours").add("test_settle_order_with_lot", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1, { loadSN: true }),
PosSale.selectedOrderLinesHasLots("Product A", ["1001", "1002"]),
].flat(),
});
registry.category("web_tour.tours").add("test_down_payment_displayed", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.downPaymentFirstOrder("+10"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
PosSale.settleNthOrder(1),
Order.hasLine({
productName: "Down Payment",
quantity: "1.0",
price: "-1.15",
}),
].flat(),
});
registry.category("web_tour.tours").add("test_sale_order_fp_different_from_partner_one", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleSaleOrderByPrice("20.00"),
ProductScreen.checkTaxAmount("10.00"),
ProductScreen.checkFiscalPosition("Partner FP"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.receiptIsThere(),
ReceiptScreen.clickNextOrder(),
PosSale.settleSaleOrderByPrice("10.00"),
ProductScreen.checkTaxAmount("0.00"),
ProductScreen.checkFiscalPosition("Sale Order FP"),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.receiptIsThere(),
ReceiptScreen.clickNextOrder(),
].flat(),
});
registry.category("web_tour.tours").add("test_quantity_updated_settle", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
ProductScreen.clickNumpad("2"),
Order.hasLine({ productName: "Product A", quantity: "2.0" }),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.clickNextOrder(),
PosSale.settleNthOrder(1),
Order.hasLine({
productName: "Product A",
quantity: "3.0",
price: "34.50",
}),
].flat(),
});
registry.category("web_tour.tours").add("test_multiple_lots_sale_order_1", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1),
Order.hasLine({ productName: "Product", quantity: "6.0" }),
].flat(),
});
registry.category("web_tour.tours").add("test_multiple_lots_sale_order_2", {
steps: () =>
[
Chrome.startPoS(),
PosSale.settleNthOrder(1, { loadSN: false }),
Order.hasLine({ productName: "Product", quantity: "6.0" }),
{
content: "Check that the line-lot-icon has text-danger class",
trigger: `.order-container .orderline:has(.product-name:contains("Product")) .line-lot-icon.text-danger`,
},
].flat(),
});
registry.category("web_tour.tours").add("test_multiple_lots_sale_order_3", {
steps: () =>
[
Chrome.startPoS(),
PosSale.settleNthOrder(1, { loadSN: true }),
PosSale.selectedOrderLinesHasLots("Product", ["1002"]),
Utils.negateStep(...PosSale.selectedOrderLinesHasLots("Product", ["1001"])),
ProductScreen.selectedOrderlineHas("Product", "2.00"),
ProductScreen.clickOrderline("Product", "4"),
PosSale.selectedOrderLinesHasLots("Product", ["1001"]),
ProductScreen.selectedOrderlineHas("Product", "4.00"),
Utils.negateStep(...PosSale.selectedOrderLinesHasLots("Product", ["1002"])),
ProductScreen.clickPayButton(),
PaymentScreen.clickPaymentMethod("Bank"),
PaymentScreen.clickValidate(),
ReceiptScreen.isShown(),
].flat(),
});
registry.category("web_tour.tours").add("test_selected_partner_quotation_loading", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A Test Partner 1"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Product A", "1.00"),
Chrome.createFloatingOrder(),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A Test Partner 2"),
PosSale.settleNthOrder(1),
ProductScreen.selectedOrderlineHas("Product B", "2.00"),
].flat(),
});
registry.category("web_tour.tours").add("test_ecommerce_paid_order_is_hidden_in_pos", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A Test Partner 1"),
PosSale.checkOrdersListEmpty(),
].flat(),
});
registry.category("web_tour.tours").add("test_ecommerce_unpaid_order_is_shown_in_pos", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
ProductScreen.clickPartnerButton(),
ProductScreen.clickCustomer("A Test Partner 1"),
PosSale.checkOrdersListNotEmpty(),
].flat(),
});
registry.category("web_tour.tours").add("test_settle_groupable_lot_total_amount", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
PosSale.settleNthOrder(1, { loadSN: true }),
Order.hasTotal("12.00"),
].flat(),
});

View file

@ -1,201 +0,0 @@
odoo.define('pos_sale.tour', function (require) {
'use strict';
const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
const { ProductScreen } = require('pos_sale.tour.ProductScreenTourMethods');
const { ReceiptScreen } = require('pos_sale.tour.ReceiptScreenTourMethods');
const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
const Tour = require('web_tour.tour');
// signal to start generating steps
// when finished, steps can be taken from getSteps
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.check.selectedOrderlineHas('Pizza Chicken', 9);
ProductScreen.do.pressNumpad('Qty 2'); // Change the quantity of the product to 2
ProductScreen.check.selectedOrderlineHas('Pizza Chicken', 2);
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.do.clickValidate();
Chrome.do.clickTicketButton();
Tour.register('PosSettleOrder', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
// The second item in the list is the first sale.order.
ProductScreen.do.selectNthOrder(2);
ProductScreen.check.selectedOrderlineHas('product1', 1);
ProductScreen.check.totalAmountIs("10.00");
ProductScreen.do.clickQuotationButton();
// The first item in the list is the second sale.order.
// Selecting the 2nd sale.order should use a new order,
// therefore, the total amount will change.
ProductScreen.do.selectNthOrder(1);
ProductScreen.check.selectedOrderlineHas('product2', 1);
ProductScreen.check.totalAmountIs("11.00");
Tour.register('PosSettleOrderIncompatiblePartner', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.do.clickOrderline('[A001] Product A', '1');
ProductScreen.check.selectedOrderlineHas('[A001] Product A', '1.00');
ProductScreen.do.clickOrderline('[A002] Product B', '1');
ProductScreen.do.pressNumpad('Qty 0');
ProductScreen.check.selectedOrderlineHas('[A002] Product B', '0.00');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.check.remainingIs('0.0');
PaymentScreen.do.clickValidate();
ReceiptScreen.check.isShown();
Tour.register('PosSettleOrder2', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.do.clickOrderline("Product A", "1");
ProductScreen.check.selectedOrderlineHas('Product A', '1.00');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.check.remainingIs('0.0');
PaymentScreen.do.clickValidate();
ReceiptScreen.check.isShown();
Tour.register('PosSettleOrder3', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.check.totalAmountIs(40);
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.do.clickValidate();
Chrome.do.clickTicketButton();
Tour.register('PosSettleOrderRealTime', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.downPaymentFirstOrder();
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.do.clickValidate();
ReceiptScreen.do.clickNextOrder();
ProductScreen.do.clickRefund();
// Filter should be automatically 'Paid'.
TicketScreen.check.filterIs('Paid');
TicketScreen.do.selectOrder('-0001');
TicketScreen.do.clickOrderline('Down Payment');
TicketScreen.do.pressNumpad('1');
TicketScreen.do.confirmRefund();
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.do.clickValidate();
ReceiptScreen.do.clickNextOrder();
Tour.register('PosRefundDownpayment', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.check.totalAmountIs(28.98); // 3.5 * 8 * 1.15 * 90%
ProductScreen.do.clickOrderline("Product A", '0.5');
ProductScreen.check.checkOrderlinesNumber(4);
ProductScreen.check.selectedOrderlineHas('Product A', '0.5', '4.14');
Tour.register('PosSettleOrderNotGroupable', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.check.checkCustomerNotes("Customer note 2--Customer note 3");
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.do.clickValidate();
ReceiptScreen.check.checkCustomerNotes("Customer note 2--Customer note 3");
ReceiptScreen.do.clickNextOrder();
Tour.register('PosSettleOrderWithNote', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.do.clickValidate();
ReceiptScreen.do.clickNextOrder();
ProductScreen.do.clickQuotationButton();
ProductScreen.check.checkOrdersListEmpty();
Tour.register('PosOrderDoesNotRemainInList', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.check.selectedOrderlineHas('product_a', '1', '100');
ProductScreen.do.clickPartnerButton();
ProductScreen.do.clickCustomer('partner_a');
ProductScreen.check.selectedOrderlineHas('product_a', '1', '100');
Tour.register('PosSettleCustomPrice', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.check.selectedOrderlineHas('Test service product', '1.00', '50.00');
Tour.register('PosSettleDraftOrder', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.selectFirstOrder();
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickShipLaterButton()
PaymentScreen.do.clickPaymentMethod('Bank');
PaymentScreen.check.remainingIs('0.0');
PaymentScreen.do.clickValidate();
ReceiptScreen.check.isShown();
Tour.register('PosSettleOrderShipLater', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.confirmOpeningPopup();
ProductScreen.do.clickQuotationButton();
ProductScreen.do.downPaymentFirstOrder();
ProductScreen.check.selectedOrderlineHas('Down Payment', '1', '10.00');
ProductScreen.do.clickPayButton();
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.do.clickValidate();
ReceiptScreen.do.clickNextOrder();
Tour.register('PoSDownPaymentAmount', { test: true, url: '/pos/ui' }, getSteps());
});

View file

@ -0,0 +1,324 @@
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 { escapeRegExp } from "@web/core/utils/strings";
import { registry } from "@web/core/registry";
export function clickDownPaymentNumpad(num) {
return {
content: `click discount numpad button: ${num}`,
trigger: `.o_dialog div.numpad button:contains(/^${escapeRegExp(num)}$/)`,
run: "click",
};
}
export function addDownPayment(percentage, soNth, downPaymentType) {
const steps = [
ProductScreen.clickControlButton("Quotation/Order"),
{
content: "Select the first SO",
trigger: `.o_sale_order .o_data_row:nth-child(${soNth}) .o_data_cell:nth-child(1)`,
run: "click",
},
];
if (downPaymentType === "percent") {
steps.push({
content: "Select 'Apply a down payment (percentage)'",
trigger: ".modal-body button:contains('percentage')",
run: "click",
});
} else {
steps.push({
content: "Select 'Apply a down payment (fixed amount)'",
trigger: ".modal-body button:contains('fixed amount')",
run: "click",
});
}
for (const num of percentage.split("")) {
steps.push(clickDownPaymentNumpad(num));
}
steps.push({
content: "Select 'Apply'",
trigger: ".modal-dialog button.btn-primary:contains('Apply')",
run: "click",
});
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_downpayment_round_per_line_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.10"),
...payAndInvoice("0.73"),
...addDownPayment("0.73", 2, "fixed"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.10"),
...payAndInvoice("0.73"),
...addDownPayment("7", 3, "percent"),
ProductScreen.checkTotalAmount("2.56"),
ProductScreen.checkTaxAmount("0.33"),
...payAndInvoice("2.56"),
...addDownPayment("2.56", 4, "fixed"),
ProductScreen.checkTotalAmount("2.56"),
ProductScreen.checkTaxAmount("0.33"),
...payAndInvoice("2.56"),
...addDownPayment("18", 5, "percent"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
...addDownPayment("6.60", 6, "fixed"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_in_pos_downpayment_round_globally_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.1"),
...payAndInvoice("0.73"),
...addDownPayment("0.73", 2, "fixed"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.1"),
...payAndInvoice("0.73"),
...addDownPayment("7", 3, "percent"),
ProductScreen.checkTotalAmount("2.57"),
ProductScreen.checkTaxAmount("0.33"),
...payAndInvoice("2.57"),
...addDownPayment("2.57", 4, "fixed"),
ProductScreen.checkTotalAmount("2.57"),
ProductScreen.checkTaxAmount("0.33"),
...payAndInvoice("2.57"),
...addDownPayment("18", 5, "percent"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
...addDownPayment("6.60", 6, "fixed"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_in_pos_downpayment_round_per_line_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.10"),
...payAndInvoice("0.73"),
...addDownPayment("0.73", 2, "fixed"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.10"),
...payAndInvoice("0.73"),
...addDownPayment("7", 3, "percent"),
ProductScreen.checkTotalAmount("2.56"),
ProductScreen.checkTaxAmount("0.33"),
...payAndInvoice("2.56"),
...addDownPayment("2.56", 4, "fixed"),
ProductScreen.checkTotalAmount("2.56"),
ProductScreen.checkTaxAmount("0.33"),
...payAndInvoice("2.56"),
...addDownPayment("18", 5, "percent"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
...addDownPayment("6.60", 6, "fixed"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_in_pos_downpayment_round_globally_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.10"),
...payAndInvoice("0.73"),
...addDownPayment("0.73", 2, "fixed"),
ProductScreen.checkTotalAmount("0.73"),
ProductScreen.checkTaxAmount("0.10"),
...payAndInvoice("0.73"),
...addDownPayment("7", 3, "percent"),
ProductScreen.checkTotalAmount("2.57"),
ProductScreen.checkTaxAmount("0.33"),
...payAndInvoice("2.57"),
...addDownPayment("2.57", 4, "fixed"),
ProductScreen.checkTotalAmount("2.57"),
ProductScreen.checkTaxAmount("0.34"),
...payAndInvoice("2.57"),
...addDownPayment("18", 5, "percent"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
...addDownPayment("6.60", 6, "fixed"),
ProductScreen.checkTotalAmount("6.60"),
ProductScreen.checkTaxAmount("0.87"),
...payAndInvoice("6.60"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_downpayment_round_per_line_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("1.92"),
ProductScreen.checkTaxAmount("0.63"),
...payAndInvoice("1.92"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_downpayment_round_globally_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("1.92"),
ProductScreen.checkTaxAmount("0.63"),
...payAndInvoice("1.92"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_downpayment_round_per_line_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("1.92"),
ProductScreen.checkTaxAmount("0.63"),
...payAndInvoice("1.92"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_br_pos_downpayment_round_globally_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("1.92"),
ProductScreen.checkTaxAmount("0.63"),
...payAndInvoice("1.92"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_downpayment_round_per_line_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.86"),
ProductScreen.checkTaxAmount("0.15"),
...payAndInvoice("0.86"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_downpayment_round_globally_price_excluded", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.86"),
ProductScreen.checkTaxAmount("0.15"),
...payAndInvoice("0.86"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_downpayment_round_per_line_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.86"),
ProductScreen.checkTaxAmount("0.15"),
...payAndInvoice("0.86"),
].flat(),
});
registry
.category("web_tour.tours")
.add("test_taxes_l10n_be_pos_downpayment_round_globally_price_included", {
steps: () =>
[
Chrome.startPoS(),
Dialog.confirm("Open Register"),
...addDownPayment("2", 1, "percent"),
ProductScreen.checkTotalAmount("0.86"),
ProductScreen.checkTaxAmount("0.15"),
...payAndInvoice("0.86"),
].flat(),
});

View file

@ -0,0 +1,97 @@
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 Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
export function selectNthOrder(n) {
return [
...ProductScreen.clickControlButton("Quotation/Order"),
{
content: `select nth order`,
trigger: `.modal:not(.o_inactive_modal) table.o_list_table tbody tr.o_data_row:nth-child(${n}) td`,
run: "click",
},
];
}
export function settleSaleOrderByPrice(price) {
return [
...ProductScreen.clickControlButton("Quotation/Order"),
{
content: `select sale order with price ${price}`,
trigger: `.modal:not(.o_inactive_modal) table.o_list_table tbody tr.o_data_row td:contains('${price}')`,
run: "click",
},
{
content: `Choose to settle the order`,
trigger: `.modal:not(.o_inactive_modal) .selection-item:contains('Settle the order')`,
run: "click",
},
];
}
export function settleNthOrder(n, options = {}) {
const { loadSN } = options;
const step = [
...selectNthOrder(n),
{
content: `Choose to settle the order`,
trigger: `.modal:not(.o_inactive_modal) .selection-item:contains('Settle the order')`,
run: "click",
},
];
if (loadSN !== undefined) {
step.push({
content: `Choose to auto link the lot number to the order line`,
trigger: `.modal-content:contains('Do you want to load the SN/Lots linked to the Sales Order?') button:contains('${
loadSN ? "Ok" : "Cancel"
}')`,
run: "click",
});
}
step.push({
trigger: "body:not(:has(.modal))",
});
return step;
}
export function downPaymentFirstOrder(amount) {
return [
...selectNthOrder(1),
{
content: `click on select the order`,
trigger: `.selection-item:contains('Apply a down payment')`,
run: "click",
},
Numpad.click(amount),
Dialog.confirm("Apply"),
];
}
export function checkOrdersListEmpty() {
return [
...ProductScreen.clickControlButton("Quotation/Order"),
{
content: "Check that the orders list is empty",
trigger: "p:contains(No record found)",
},
];
}
export function selectedOrderLinesHasLots(productName, lots) {
const getSerialStep = (index, serialNumber) => ({
content: `check lot${index} is linked`,
trigger: `.info-list li:contains(${serialNumber})`,
});
const lotSteps = lots.reduce((acc, serial, i) => acc.concat(getSerialStep(i, serial)), []);
return [...ProductScreen.selectedOrderlineHas(productName), ...lotSteps];
}
export function checkOrdersListNotEmpty() {
return [
...ProductScreen.clickControlButton("Quotation/Order"),
{
content: "Check that the orders list is not empty",
trigger: ".o_data_row",
},
];
}

View file

@ -0,0 +1,55 @@
import { test, expect } from "@odoo/hoot";
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
import { Orderline } from "@point_of_sale/app/components/orderline/orderline";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
test("Displays the table with details of the down payment", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const productDownPayment = store.models["product.template"].get(105);
const sol1 = store.models["sale.order.line"].get(1);
const sol2 = store.models["sale.order.line"].get(2);
const line = await store.addLineToOrder(
{
product_tmpl_id: productDownPayment,
sale_order_origin_id: 1,
down_payment_details: [
{
product_name: sol1.display_name,
product_uom_qty: sol1.product_uom_qty,
price_unit: sol1.price_unit,
total: sol1.price_total,
},
{
product_name: sol2.display_name,
product_uom_qty: sol2.product_uom_qty,
price_unit: sol2.price_unit,
total: sol2.price_total,
},
],
qty: 1,
},
order
);
const comp = await mountWithCleanup(Orderline, { props: { line } });
const saleOrderInfo = ".orderline .info-list .sale-order-info";
const cell = (tr, td) => `${saleOrderInfo} tr:nth-child(${tr}) td:nth-child(${td})`;
expect(comp.line).toEqual(line);
expect(saleOrderInfo).toBeVisible();
expect(`${saleOrderInfo} tr`).toHaveCount(2);
expect(cell(1, 1)).toHaveText("5x");
expect(cell(1, 2)).toHaveText("Product 1");
expect(cell(1, 4)).toHaveText(`$ 500.00 (tax incl.)`);
expect(cell(2, 1)).toHaveText("3x");
expect(cell(2, 2)).toHaveText("Product 2");
expect(cell(2, 4)).toHaveText(`$ 150.00 (tax incl.)`);
});

View file

@ -0,0 +1,11 @@
import { PosConfig } from "@point_of_sale/../tests/unit/data/pos_config.data";
PosConfig._records = PosConfig._records.map((config) => {
if (config.id === 1) {
return {
...config,
down_payment_product_id: 105,
};
}
return config;
});

View file

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

View file

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

View file

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

View file

@ -0,0 +1,46 @@
import { patch } from "@web/core/utils/patch";
import { ProductTemplate } from "@point_of_sale/../tests/unit/data/product_template.data";
patch(ProductTemplate.prototype, {
_load_pos_data_fields() {
return [...super._load_pos_data_fields(), "sale_line_warn_msg", "invoice_policy"];
},
});
ProductTemplate._records = [
...ProductTemplate._records,
{
id: 105,
display_name: "Down Payment (POS)",
standard_price: 0,
categ_id: false,
pos_categ_ids: [],
taxes_id: [],
barcode: false,
name: "Down Payment (POS)",
list_price: 0,
is_favorite: false,
default_code: false,
to_weight: false,
uom_id: 1,
description_sale: false,
description: false,
tracking: "none",
type: "service",
service_tracking: "no",
is_storable: false,
write_date: "2025-07-03 17:04:14",
color: 0,
pos_sequence: 5,
available_in_pos: true,
attribute_line_ids: [],
active: true,
image_128: false,
sequence: 1,
combo_ids: [],
product_variant_ids: [7],
public_description: false,
pos_optional_product_ids: [],
product_tag_ids: [],
},
];

View file

@ -0,0 +1,8 @@
import { patch } from "@web/core/utils/patch";
import { ResPartner as MailResPartner } from "@mail/../tests/mock_server/mock_models/res_partner";
patch(MailResPartner.prototype, {
_load_pos_data_fields() {
return [...super._load_pos_data_fields(), "sale_warn_msg"];
},
});

View file

@ -0,0 +1,69 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
export class SaleOrder extends models.ServerModel {
_name = "sale.order";
_load_pos_data_fields() {
return [
"name",
"state",
"user_id",
"order_line",
"partner_id",
"pricelist_id",
"fiscal_position_id",
"amount_total",
"amount_untaxed",
"amount_unpaid",
"picking_ids",
"partner_shipping_id",
"partner_invoice_id",
"date_order",
"write_date",
];
}
_records = [
{
id: 1,
name: "S00001",
state: "sale",
order_line: [1, 2],
partner_id: 3,
pricelist_id: 1,
fiscal_position_id: 1,
amount_total: 650,
amount_untaxed: 500,
amount_unpaid: 650,
partner_shipping_id: 3,
partner_invoice_id: 3,
date_order: "2025-07-03 17:04:14",
write_date: "2025-07-03 17:04:14",
},
];
async load_sale_order_from_pos(id, config_id) {
const order = this.env["sale.order"].find((order) => order.id === id);
const orderLines = this.env["sale.order.line"].filter((line) =>
order.order_line.includes(line.id)
);
const partner = this.env["res.partner"].find((partner) => partner.id === order.partner_id);
const productProducts = this.env["product.product"].filter((product) =>
orderLines.map((line) => line.product_id).includes(product.id)
);
const productTemplates = this.env["product.template"].filter((template) =>
productProducts.map((p) => p.product_tmpl_id).includes(template.id)
);
return {
"sale.order": [order],
"sale.order.line": orderLines,
"res.partner": [partner],
"product.product": productProducts,
"product.template": productTemplates,
};
}
}
patch(hootPosModels, [...hootPosModels, SaleOrder]);

View file

@ -0,0 +1,83 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models, MockServer } from "@web/../tests/web_test_helpers";
export class SaleOrderLine extends models.ServerModel {
_name = "sale.order.line";
_load_pos_data_fields() {
return [
"discount",
"display_name",
"price_total",
"price_unit",
"product_id",
"product_uom_qty",
"qty_delivered",
"qty_invoiced",
"qty_to_invoice",
"display_type",
"name",
"tax_ids",
"is_downpayment",
"extra_tax_data",
"write_date",
"is_repair_line",
];
}
_records = [
{
id: 1,
display_name: "Product 1",
product_id: 5,
product_uom_qty: 5,
price_unit: 100,
price_total: 500,
discount: 0,
qty_delivered: 0,
qty_invoiced: 0,
qty_to_invoice: 5,
display_type: false,
name: "Product 1",
tax_ids: [],
is_downpayment: false,
extra_tax_data: {},
write_date: "2025-07-03 17:04:14",
},
{
id: 2,
display_name: "Product 2",
product_id: 6,
product_uom_qty: 3,
price_unit: 50,
price_total: 150,
discount: 0,
qty_delivered: 0,
qty_invoiced: 0,
qty_to_invoice: 3,
display_type: false,
name: "Product 2",
tax_ids: [],
is_downpayment: false,
extra_tax_data: {},
write_date: "2025-07-03 17:04:14",
},
];
async read_converted(ids) {
const model = MockServer.env[this._name];
const posFields = model._load_pos_data_fields();
const records = model.search_read(
[["id", "in", ids]],
posFields,
false,
false,
false,
false
);
return records;
}
}
patch(hootPosModels, [...hootPosModels, SaleOrderLine]);

View file

@ -0,0 +1,118 @@
import { test, expect, describe } from "@odoo/hoot";
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
describe("saleDetails", () => {
test("down payment details as array", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const productDownPayment = store.models["product.template"].get(105);
const line = await store.addLineToOrder(
{
product_tmpl_id: productDownPayment,
down_payment_details: [
{
product_name: "Product 1",
product_uom_qty: 2,
total: 100,
},
{
product_name: "Product 2",
product_uom_qty: 1,
total: 50,
},
],
qty: 1,
},
order
);
const saleDetails = line.saleDetails;
expect(saleDetails).toEqual([
{
product_uom_qty: 2,
product_name: "Product 1",
total: "$\u00a0100.00",
},
{
product_uom_qty: 1,
product_name: "Product 2",
total: "$\u00a050.00",
},
]);
});
test("down payment details as stringified JSON", async () => {
const store = await setupPosEnv();
const order = store.addNewOrder();
const productDownPayment = store.models["product.template"].get(105);
const line = await store.addLineToOrder(
{
product_tmpl_id: productDownPayment,
down_payment_details: JSON.stringify([
{
product_name: "Product 1",
product_uom_qty: 2,
total: 100,
},
{
product_name: "Product 2",
product_uom_qty: 1,
total: 50,
},
]),
qty: 1,
},
order
);
const saleDetails = line.saleDetails;
expect(saleDetails).toEqual([
{
product_uom_qty: 2,
product_name: "Product 1",
total: "$\u00a0100.00",
},
{
product_uom_qty: 1,
product_name: "Product 2",
total: "$\u00a050.00",
},
]);
});
});
describe("setQuantityFromSOL", () => {
test("service product, state != sent/draft → qty_to_invoice", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const line = order.lines[0];
line.product_id.type = "service";
line.sale_order_origin_id = { state: "sale" }; // not 'sent' or 'draft'
const saleOrderLine = { qty_to_invoice: 2 };
await line.setQuantityFromSOL(saleOrderLine);
expect(line.qty).toBe(2);
});
test("non-service product → qty = uom_qty - max(delivered, invoiced)", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
const line = order.lines[0];
line.product_id.type = "consu";
const saleOrderLine = {
product_uom_qty: 8,
qty_delivered: 1,
qty_invoiced: 2,
};
await line.setQuantityFromSOL(saleOrderLine);
expect(line.qty).toBe(6); // 8 - max(1,2)
});
});

View file

@ -0,0 +1,180 @@
import { test, expect, describe } from "@odoo/hoot";
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
import { click, waitFor } from "@odoo/hoot-dom";
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
import { Orderline } from "@point_of_sale/app/components/orderline/orderline";
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
definePosModels();
describe("onClickSaleOrder", () => {
test("no selection → abort", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
await mountWithCleanup(ProductScreen, { props: { orderUuid: order.uuid } });
const promiseResult = store.onClickSaleOrder(1);
const button =
".modal-header:has(.modal-title:contains('What do you want to do?')) button[aria-label='Close']";
await waitFor(button);
await click(button);
await promiseResult;
const currentOrder = store.getOrder();
expect(currentOrder.id).toBe(order.id);
expect(currentOrder.lines.length).toBe(2);
expect(currentOrder.lines[0].product_id.id).toBe(5);
expect(currentOrder.lines[0].qty).toBe(3);
expect(currentOrder.lines[1].product_id.id).toBe(6);
expect(currentOrder.lines[1].qty).toBe(2);
});
test("settle → calls settleSO", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
await mountWithCleanup(ProductScreen, { props: { orderUuid: order.uuid } });
const promiseResult = store.onClickSaleOrder(1);
const button = ".modal-body button:contains('Settle the order')";
await waitFor(button);
await click(button);
await promiseResult;
const currentOrder = store.getOrder();
expect(currentOrder.id).toBe(order.id);
expect(currentOrder.lines.length).toBe(4);
expect(currentOrder.lines[0].product_id.id).toBe(5);
expect(currentOrder.lines[0].qty).toBe(3);
expect(currentOrder.lines[0].price_unit).toBe(3);
expect(currentOrder.lines[0].prices.total_excluded).toBe(9);
expect(currentOrder.lines[1].product_id.id).toBe(6);
expect(currentOrder.lines[1].qty).toBe(2);
expect(currentOrder.lines[1].price_unit).toBe(3);
expect(currentOrder.lines[1].prices.total_excluded).toBe(6);
expect(currentOrder.lines[2].product_id.id).toBe(5);
expect(currentOrder.lines[2].qty).toBe(5);
expect(currentOrder.lines[2].price_unit).toBe(100);
expect(currentOrder.lines[2].prices.total_excluded).toBe(500);
expect(currentOrder.lines[3].product_id.id).toBe(6);
expect(currentOrder.lines[3].qty).toBe(3);
expect(currentOrder.lines[3].price_unit).toBe(50);
expect(currentOrder.lines[3].prices.total_excluded).toBe(150);
});
test("dpPercentage → calls downPaymentSO", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
await mountWithCleanup(ProductScreen, { props: { orderUuid: order.uuid } });
const promiseResult = store.onClickSaleOrder(1);
const buttonDownPaymentPercentage =
".modal-body button:contains('Apply a down payment (percentage)')";
await waitFor(buttonDownPaymentPercentage);
await click(buttonDownPaymentPercentage);
await waitFor(".modal-title:contains('Down Payment')");
await click(".modal-body .numpad .numpad-button[value='+50']");
await new Promise((resolve) => setTimeout(resolve, 50));
await click(".modal-footer .btn:contains('Apply')");
await promiseResult;
const currentOrder = store.getOrder();
expect(currentOrder.id).toBe(order.id);
expect(currentOrder.lines.length).toBe(3);
expect(currentOrder.lines[0].product_id.id).toBe(5);
expect(currentOrder.lines[0].qty).toBe(3);
expect(currentOrder.lines[0].price_unit).toBe(3);
expect(currentOrder.lines[0].prices.total_excluded).toBe(9);
expect(currentOrder.lines[1].product_id.id).toBe(6);
expect(currentOrder.lines[1].qty).toBe(2);
expect(currentOrder.lines[1].price_unit).toBe(3);
expect(currentOrder.lines[1].prices.total_excluded).toBe(6);
expect(currentOrder.lines[2].product_id.id).toBe(105);
expect(currentOrder.lines[2].qty).toBe(1);
expect(currentOrder.lines[2].price_unit).toBe(325);
expect(currentOrder.lines[2].prices.total_excluded).toBe(325);
const comp = await mountWithCleanup(Orderline, {
props: { line: currentOrder.lines[2] },
});
const saleOrderInfo = ".orderline .info-list .sale-order-info";
const cell = (tr, td) => `${saleOrderInfo} tr:nth-child(${tr}) td:nth-child(${td})`;
expect(comp.line).toEqual(currentOrder.lines[2]);
expect(`${saleOrderInfo} tr`).toHaveCount(4);
expect(cell(1, 1)).toHaveText("5x");
expect(cell(1, 2)).toHaveText("TEST");
expect(cell(1, 4)).toHaveText(`$ 500.00 (tax incl.)`);
expect(cell(2, 1)).toHaveText("3x");
expect(cell(2, 2)).toHaveText("TEST 2");
expect(cell(2, 4)).toHaveText(`$ 150.00 (tax incl.)`);
});
test("dpAmount → calls downPaymentSO", async () => {
const store = await setupPosEnv();
const order = await getFilledOrder(store);
await mountWithCleanup(ProductScreen, { props: { orderUuid: order.uuid } });
const promiseResult = store.onClickSaleOrder(1);
const buttonDownPaymentPercentage =
".modal-body button:contains('Apply a down payment (fixed amount)')";
await waitFor(buttonDownPaymentPercentage);
await click(buttonDownPaymentPercentage);
await waitFor(".modal-title:contains('Down Payment')");
await click(".modal-body .numpad .numpad-button[value='+50']");
await new Promise((resolve) => setTimeout(resolve, 50));
await click(".modal-footer .btn:contains('Apply')");
await promiseResult;
const currentOrder = store.getOrder();
expect(currentOrder.id).toBe(order.id);
expect(currentOrder.lines.length).toBe(3);
expect(currentOrder.lines[0].product_id.id).toBe(5);
expect(currentOrder.lines[0].qty).toBe(3);
expect(currentOrder.lines[0].price_unit).toBe(3);
expect(currentOrder.lines[0].prices.total_excluded).toBe(9);
expect(currentOrder.lines[1].product_id.id).toBe(6);
expect(currentOrder.lines[1].qty).toBe(2);
expect(currentOrder.lines[1].price_unit).toBe(3);
expect(currentOrder.lines[1].prices.total_excluded).toBe(6);
expect(currentOrder.lines[2].product_id.id).toBe(105);
expect(currentOrder.lines[2].qty).toBe(1);
expect(currentOrder.lines[2].price_unit).toBe(50);
expect(currentOrder.lines[2].prices.total_excluded).toBe(50);
const comp = await mountWithCleanup(Orderline, {
props: { line: currentOrder.lines[2] },
});
const saleOrderInfo = ".orderline .info-list .sale-order-info";
const cell = (tr, td) => `${saleOrderInfo} tr:nth-child(${tr}) td:nth-child(${td})`;
expect(comp.line).toEqual(currentOrder.lines[2]);
expect(`${saleOrderInfo} tr`).toHaveCount(4);
expect(cell(1, 1)).toHaveText("5x");
expect(cell(1, 2)).toHaveText("TEST");
expect(cell(1, 4)).toHaveText(`$ 500.00 (tax incl.)`);
expect(cell(2, 1)).toHaveText("3x");
expect(cell(2, 2)).toHaveText("TEST 2");
expect(cell(2, 4)).toHaveText(`$ 150.00 (tax incl.)`);
});
});