mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 02:12:05 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
|
|
@ -0,0 +1,55 @@
|
|||
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());
|
||||
});
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
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());
|
||||
});
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
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());
|
||||
});
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
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());
|
||||
});
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
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());
|
||||
});
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
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());
|
||||
});
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
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());
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,429 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
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);
|
||||
});
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
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 };
|
||||
});
|
||||
|
|
@ -0,0 +1,408 @@
|
|||
/* 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,34 @@
|
|||
odoo.define('point_of_sale.test_env', async function (require) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Many components in PoS are dependent on the PosGlobalState instance (pos).
|
||||
* Therefore, for unit tests that require pos in the Components' env, we
|
||||
* prepared here a test env maker (makePosTestEnv) based on
|
||||
* makeTestEnvironment of web.
|
||||
*/
|
||||
|
||||
const makeTestEnvironment = require('web.test_env');
|
||||
const env = require('point_of_sale.env');
|
||||
const { PosGlobalState } = require('point_of_sale.models');
|
||||
const cleanup = require("@web/../tests/helpers/cleanup");
|
||||
|
||||
// We override this method in the pos unit tests to prevent the unnecessary error in the web tests.
|
||||
cleanup.registerCleanup = () => {}
|
||||
|
||||
await env.session.is_bound;
|
||||
const pos = PosGlobalState.create({ env });
|
||||
await pos.load_server_data();
|
||||
|
||||
/**
|
||||
* @param {Object} env default env
|
||||
* @param {Function} providedRPC mock rpc
|
||||
* @param {Function} providedDoAction mock do_action
|
||||
*/
|
||||
function makePosTestEnv(env = {}, providedRPC = null, providedDoAction = null) {
|
||||
env = Object.assign(env, { pos });
|
||||
return makeTestEnvironment(env, providedRPC);
|
||||
}
|
||||
|
||||
return makePosTestEnv;
|
||||
});
|
||||
|
|
@ -0,0 +1,414 @@
|
|||
odoo.define('point_of_sale.tests.ComponentRegistry', function(require) {
|
||||
'use strict';
|
||||
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
QUnit.module('unit tests for ComponentRegistry', {
|
||||
before() {},
|
||||
});
|
||||
|
||||
QUnit.test('basic extend', async function(assert) {
|
||||
assert.expect(5);
|
||||
|
||||
class A {
|
||||
constructor() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
Registries.Component.add(A);
|
||||
|
||||
let A1 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A1');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A1);
|
||||
|
||||
Registries.Component.freeze();
|
||||
|
||||
const RegA = Registries.Component.get(A);
|
||||
let a = new RegA();
|
||||
assert.verifySteps(['A', 'A1']);
|
||||
assert.ok(a instanceof RegA);
|
||||
assert.ok(RegA.name === 'A');
|
||||
});
|
||||
|
||||
QUnit.test('addByExtending', async function(assert) {
|
||||
assert.expect(8);
|
||||
|
||||
class A {
|
||||
constructor() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
Registries.Component.add(A);
|
||||
|
||||
let B = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(B, A);
|
||||
|
||||
let A1 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A1');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A1);
|
||||
|
||||
let A2 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A2');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A2);
|
||||
|
||||
Registries.Component.freeze();
|
||||
|
||||
const RegA = Registries.Component.get(A);
|
||||
const RegB = Registries.Component.get(B);
|
||||
let b = new RegB();
|
||||
assert.verifySteps(['A', 'A1', 'A2', 'B']);
|
||||
assert.ok(b instanceof RegA);
|
||||
assert.ok(b instanceof RegB);
|
||||
assert.ok(RegB.name === 'B');
|
||||
});
|
||||
|
||||
QUnit.test('extend the one that is added by extending', async function(assert) {
|
||||
assert.expect(6);
|
||||
|
||||
class A {
|
||||
constructor() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
Registries.Component.add(A);
|
||||
|
||||
let B = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(B, A);
|
||||
|
||||
let B1 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B1');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(B, B1);
|
||||
|
||||
let B2 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B2');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(B, B2);
|
||||
|
||||
let A1 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A1');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A1);
|
||||
|
||||
Registries.Component.freeze();
|
||||
|
||||
const RegB = Registries.Component.get(B);
|
||||
new RegB();
|
||||
assert.verifySteps(['A', 'A1', 'B', 'B1', 'B2']);
|
||||
});
|
||||
|
||||
QUnit.test('addByExtending based on added by extending', async function(assert) {
|
||||
assert.expect(10);
|
||||
|
||||
class A {
|
||||
constructor() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
Registries.Component.add(A);
|
||||
|
||||
let B = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(B, A);
|
||||
|
||||
let A1 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A1');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A1);
|
||||
|
||||
let C = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('C');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(C, B);
|
||||
|
||||
let B7 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B7');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(B, B7);
|
||||
|
||||
Registries.Component.freeze();
|
||||
|
||||
const RegA = Registries.Component.get(A);
|
||||
const RegB = Registries.Component.get(B);
|
||||
const RegC = Registries.Component.get(C);
|
||||
let c = new RegC();
|
||||
assert.verifySteps(['A', 'A1', 'B', 'B7', 'C']);
|
||||
assert.ok(c instanceof RegA);
|
||||
assert.ok(c instanceof RegB);
|
||||
assert.ok(c instanceof RegC);
|
||||
assert.ok(RegC.name === 'C');
|
||||
});
|
||||
|
||||
QUnit.test('deeper inheritance', async function(assert) {
|
||||
assert.expect(9);
|
||||
|
||||
class A {
|
||||
constructor() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
Registries.Component.add(A);
|
||||
|
||||
let B = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(B, A);
|
||||
|
||||
let A1 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A1');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A1);
|
||||
|
||||
let C = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('C');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(C, B);
|
||||
|
||||
let B2 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B2');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(B, B2);
|
||||
|
||||
let B3 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B3');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(B, B3);
|
||||
|
||||
let A9 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A9');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A9);
|
||||
|
||||
let E = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('E');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(E, C);
|
||||
|
||||
Registries.Component.freeze();
|
||||
|
||||
// |A| => A9 -> A1 -> A
|
||||
// |B| => B3 -> B2 -> B -> |A|
|
||||
// |C| => C -> |B|
|
||||
// |E| => E -> |C|
|
||||
|
||||
new (Registries.Component.get(E))();
|
||||
assert.verifySteps(['A', 'A1', 'A9', 'B', 'B2', 'B3', 'C', 'E']);
|
||||
});
|
||||
|
||||
QUnit.test('mixins?', async function(assert) {
|
||||
assert.expect(12);
|
||||
|
||||
class A {
|
||||
constructor() {
|
||||
assert.step('A');
|
||||
}
|
||||
}
|
||||
Registries.Component.add(A);
|
||||
|
||||
let Mixin = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('Mixin');
|
||||
}
|
||||
mixinMethod() {
|
||||
return 'mixinMethod';
|
||||
}
|
||||
get mixinGetter() {
|
||||
return 'mixinGetter';
|
||||
}
|
||||
};
|
||||
|
||||
// use the mixin when declaring B.
|
||||
let B = x =>
|
||||
class extends Mixin(x) {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('B');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(B, A);
|
||||
|
||||
let A1 = x =>
|
||||
class extends x {
|
||||
constructor() {
|
||||
super();
|
||||
assert.step('A1');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A1);
|
||||
|
||||
Registries.Component.freeze();
|
||||
|
||||
B = Registries.Component.get(B);
|
||||
const b = new B();
|
||||
assert.verifySteps(['A', 'A1', 'Mixin', 'B']);
|
||||
// instance of B should have the mixin properties
|
||||
assert.strictEqual(b.mixinMethod(), 'mixinMethod');
|
||||
assert.strictEqual(b.mixinGetter, 'mixinGetter');
|
||||
|
||||
// instance of A should not have the mixin properties
|
||||
A = Registries.Component.get(A);
|
||||
const a = new A();
|
||||
assert.verifySteps(['A', 'A1']);
|
||||
assert.notOk(a.mixinMethod);
|
||||
assert.notOk(a.mixinGetter);
|
||||
});
|
||||
|
||||
QUnit.test('extending methods', async function(assert) {
|
||||
assert.expect(16);
|
||||
|
||||
class A {
|
||||
foo() {
|
||||
assert.step('A foo');
|
||||
}
|
||||
}
|
||||
Registries.Component.add(A);
|
||||
|
||||
let B = x =>
|
||||
class extends x {
|
||||
bar() {
|
||||
assert.step('B bar');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(B, A);
|
||||
|
||||
let A1 = x =>
|
||||
class extends x {
|
||||
bar() {
|
||||
assert.step('A1 bar');
|
||||
// should only be for A.
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(A, A1);
|
||||
|
||||
let B1 = x =>
|
||||
class extends x {
|
||||
foo() {
|
||||
super.foo();
|
||||
assert.step('B1 foo');
|
||||
}
|
||||
};
|
||||
Registries.Component.extend(B, B1);
|
||||
|
||||
let C = x =>
|
||||
class extends x {
|
||||
foo() {
|
||||
super.foo();
|
||||
assert.step('C foo');
|
||||
}
|
||||
bar() {
|
||||
super.bar();
|
||||
assert.step('C bar');
|
||||
}
|
||||
};
|
||||
Registries.Component.addByExtending(C, B);
|
||||
|
||||
Registries.Component.freeze();
|
||||
|
||||
A = Registries.Component.get(A);
|
||||
B = Registries.Component.get(B);
|
||||
C = Registries.Component.get(C);
|
||||
const a = new A();
|
||||
const b = new B();
|
||||
const c = new C();
|
||||
|
||||
a.foo();
|
||||
assert.verifySteps(['A foo']);
|
||||
b.foo();
|
||||
assert.verifySteps(['A foo', 'B1 foo']);
|
||||
c.foo();
|
||||
assert.verifySteps(['A foo', 'B1 foo', 'C foo']);
|
||||
|
||||
a.bar();
|
||||
assert.verifySteps(['A1 bar']);
|
||||
b.bar();
|
||||
assert.verifySteps(['B bar']);
|
||||
c.bar();
|
||||
assert.verifySteps(['B bar', 'C bar']);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
odoo.define('point_of_sale.tests.NumberBuffer', function(require) {
|
||||
'use strict';
|
||||
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const makeTestEnvironment = require('web.test_env');
|
||||
const testUtils = require('web.test_utils');
|
||||
const { mount } = require('@web/../tests/helpers/utils');
|
||||
const { LegacyComponent } = require("@web/legacy/legacy_component");
|
||||
|
||||
const { useState, xml } = owl;
|
||||
|
||||
QUnit.module('unit tests for NumberBuffer', {
|
||||
before() {},
|
||||
});
|
||||
|
||||
QUnit.test('simple fast inputs with capture in between', async function(assert) {
|
||||
assert.expect(3);
|
||||
const target = testUtils.prepareTarget();
|
||||
const env = makeTestEnvironment();
|
||||
|
||||
class Root extends LegacyComponent {
|
||||
setup() {
|
||||
this.state = useState({ buffer: '' });
|
||||
NumberBuffer.activate();
|
||||
NumberBuffer.use({
|
||||
nonKeyboardInputEvent: 'numpad-click-input',
|
||||
state: this.state,
|
||||
});
|
||||
}
|
||||
resetBuffer() {
|
||||
NumberBuffer.capture();
|
||||
NumberBuffer.reset();
|
||||
}
|
||||
onClickOne() {
|
||||
this.trigger('numpad-click-input', { key: '1' });
|
||||
}
|
||||
onClickTwo() {
|
||||
this.trigger('numpad-click-input', { key: '2' });
|
||||
}
|
||||
}
|
||||
Root.template = xml/* html */ `
|
||||
<div>
|
||||
<p><t t-esc="state.buffer" /></p>
|
||||
<button class="one" t-on-click="onClickOne">1</button>
|
||||
<button class="two" t-on-click="onClickTwo">2</button>
|
||||
<button class="reset" t-on-click="resetBuffer">reset</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
await mount(Root, target, { env });
|
||||
|
||||
const oneButton = target.querySelector('button.one');
|
||||
const twoButton = target.querySelector('button.two');
|
||||
const resetButton = target.querySelector('button.reset');
|
||||
const bufferEl = target.querySelector('p');
|
||||
|
||||
testUtils.dom.click(oneButton);
|
||||
testUtils.dom.click(twoButton);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(bufferEl.textContent, '12');
|
||||
testUtils.dom.click(resetButton);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(bufferEl.textContent, '');
|
||||
testUtils.dom.click(twoButton);
|
||||
testUtils.dom.click(oneButton);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(bufferEl.textContent, '21');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
odoo.define('point_of_sale.tests.PosPopupController', function(require) {
|
||||
'use strict';
|
||||
|
||||
const PosPopupController = require('point_of_sale.PosPopupController');
|
||||
const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const makeTestEnvironment = require('web.test_env');
|
||||
const testUtils = require('web.test_utils');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { mount } = require('@web/../tests/helpers/utils');
|
||||
|
||||
const { EventBus, useSubEnv, xml } = owl;
|
||||
|
||||
QUnit.module('unit tests for PosPopupController', {
|
||||
before() {
|
||||
Registries.Component.freeze();
|
||||
|
||||
// Note that we are creating new popups here to decouple this test from the pos app.
|
||||
class CustomPopup1 extends AbstractAwaitablePopup {}
|
||||
CustomPopup1.template = xml/* html */`
|
||||
<div class="popup custom-popup-1">
|
||||
<footer>
|
||||
<div class="confirm" t-on-click="confirm">
|
||||
Yes
|
||||
</div>
|
||||
<div class="cancel" t-on-click="cancel">
|
||||
No
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
|
||||
class CustomPopup2 extends AbstractAwaitablePopup {}
|
||||
CustomPopup2.template = xml/* html */`
|
||||
<div class="popup custom-popup-2">
|
||||
<footer>
|
||||
<div class="confirm" t-on-click="confirm">
|
||||
Okay
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
|
||||
PosPopupController.components = { CustomPopup1, CustomPopup2 };
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('allow multiple popups at the same time', async function(assert) {
|
||||
assert.expect(12);
|
||||
|
||||
class Root extends PosComponent {
|
||||
setup() {
|
||||
super.setup();
|
||||
useSubEnv({
|
||||
isDebug: () => false,
|
||||
posbus: new EventBus(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Root.env = makeTestEnvironment();
|
||||
Root.template = xml/* html */ `
|
||||
<div>
|
||||
<PosPopupController />
|
||||
</div>
|
||||
`;
|
||||
|
||||
const root = await mount(Root, testUtils.prepareTarget());
|
||||
|
||||
// Check 1 popup
|
||||
let popup1Promise = root.showPopup('CustomPopup1', {});
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
|
||||
testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-1 .confirm'));
|
||||
let result1 = await popup1Promise;
|
||||
assert.strictEqual(result1.confirmed, true);
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
|
||||
|
||||
// Check multiple popups
|
||||
popup1Promise = root.showPopup('CustomPopup1', {});
|
||||
await testUtils.nextTick();
|
||||
|
||||
// Check if the first popup is shown.
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
|
||||
|
||||
let popup2Promise = root.showPopup('CustomPopup2', {});
|
||||
await testUtils.nextTick();
|
||||
|
||||
// Check for the second popup.
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 2);
|
||||
|
||||
// popup 1 should be hidden
|
||||
assert.strictEqual(root.el.querySelectorAll('.modal-dialog.oe_hidden').length, 1);
|
||||
|
||||
// click confirm on popup 2
|
||||
testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-2 .confirm'));
|
||||
await testUtils.nextTick();
|
||||
|
||||
// after confirming on popup 2, only 1 should remain.
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
|
||||
assert.strictEqual(root.el.querySelectorAll('.modal-dialog .custom-popup-2').length, 0);
|
||||
|
||||
// popup 1 should not be hidden
|
||||
const CustomPopup1 = root.el.querySelector('.modal-dialog')
|
||||
assert.strictEqual(![...CustomPopup1.classList].includes('oe_hidden'), true);
|
||||
testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-1 .cancel'));
|
||||
await testUtils.nextTick();
|
||||
|
||||
// after cancelling popup 1, no popup should remain.
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
|
||||
|
||||
result1 = await popup1Promise;
|
||||
let result2 = await popup2Promise;
|
||||
assert.strictEqual(result1.confirmed, false); // false because it's cancelled.
|
||||
assert.strictEqual(result2.confirmed, true); // true because it's confirmed.
|
||||
});
|
||||
|
||||
QUnit.test('pressing cancel/confirm key should only close the top popup', async function(assert) {
|
||||
assert.expect(6);
|
||||
|
||||
class Root extends PosComponent {
|
||||
setup() {
|
||||
super.setup();
|
||||
useSubEnv({
|
||||
isDebug: () => false,
|
||||
posbus: new EventBus(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Root.env = makeTestEnvironment();
|
||||
Root.template = xml/* html */ `
|
||||
<div>
|
||||
<PosPopupController />
|
||||
</div>
|
||||
`;
|
||||
|
||||
const root = await mount(Root, testUtils.prepareTarget());
|
||||
|
||||
let popup1Promise = root.showPopup('CustomPopup1', { confirmKey: 'Enter', cancelKey: 'Escape' });
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
|
||||
|
||||
let popup2Promise = root.showPopup('CustomPopup2', { confirmKey: 'Enter', cancelKey: 'Escape' });
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 2);
|
||||
|
||||
// Pressing 'Escape' should cancel the top popup which is the CustomPopup2.
|
||||
testUtils.dom.triggerEvent(window, 'keyup', { key: 'Escape' });
|
||||
await testUtils.nextTick();
|
||||
|
||||
// Therefore, the popup2Promise has now resolved with `confirmed` value = false.
|
||||
const result2 = await popup2Promise;
|
||||
assert.strictEqual(result2.confirmed, false);
|
||||
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
|
||||
|
||||
testUtils.dom.triggerEvent(window, 'keyup', { key: 'Enter' });
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
|
||||
|
||||
const result1 = await popup1Promise;
|
||||
assert.strictEqual(result1.confirmed, true);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue