summaryrefslogtreecommitdiff
path: root/addons/point_of_sale/static/src/js/Screens/PaymentScreen
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/point_of_sale/static/src/js/Screens/PaymentScreen
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/point_of_sale/static/src/js/Screens/PaymentScreen')
-rw-r--r--addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js17
-rw-r--r--addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentMethodButton.js13
-rw-r--r--addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js376
-rw-r--r--addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenElectronicPayment.js23
-rw-r--r--addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js18
-rw-r--r--addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js23
-rw-r--r--addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js30
7 files changed, 500 insertions, 0 deletions
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js
new file mode 100644
index 00000000..b5dc6a7b
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js
@@ -0,0 +1,17 @@
+odoo.define('point_of_sale.PSNumpadInputButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class PSNumpadInputButton extends PosComponent {
+ get _class() {
+ return this.props.changeClassTo || 'input-button number-char';
+ }
+ }
+ PSNumpadInputButton.template = 'PSNumpadInputButton';
+
+ Registries.Component.add(PSNumpadInputButton);
+
+ return PSNumpadInputButton;
+});
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentMethodButton.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentMethodButton.js
new file mode 100644
index 00000000..8e5853d3
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentMethodButton.js
@@ -0,0 +1,13 @@
+odoo.define('point_of_sale.PaymentMethodButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class PaymentMethodButton extends PosComponent {}
+ PaymentMethodButton.template = 'PaymentMethodButton';
+
+ Registries.Component.add(PaymentMethodButton);
+
+ return PaymentMethodButton;
+});
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
new file mode 100644
index 00000000..6fe25a11
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
@@ -0,0 +1,376 @@
+odoo.define('point_of_sale.PaymentScreen', function (require) {
+ 'use strict';
+
+ const { parse } = require('web.field_utils');
+ const PosComponent = require('point_of_sale.PosComponent');
+ const { useErrorHandlers } = require('point_of_sale.custom_hooks');
+ const NumberBuffer = require('point_of_sale.NumberBuffer');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+ const { onChangeOrder } = require('point_of_sale.custom_hooks');
+
+ class PaymentScreen extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('delete-payment-line', this.deletePaymentLine);
+ useListener('select-payment-line', this.selectPaymentLine);
+ useListener('new-payment-line', this.addNewPaymentLine);
+ useListener('update-selected-paymentline', this._updateSelectedPaymentline);
+ useListener('send-payment-request', this._sendPaymentRequest);
+ useListener('send-payment-cancel', this._sendPaymentCancel);
+ useListener('send-payment-reverse', this._sendPaymentReverse);
+ useListener('send-force-done', this._sendForceDone);
+ NumberBuffer.use({
+ // The numberBuffer listens to this event to update its state.
+ // Basically means 'update the buffer when this event is triggered'
+ nonKeyboardInputEvent: 'input-from-numpad',
+ // When the buffer is updated, trigger this event.
+ // Note that the component listens to it.
+ triggerAtInput: 'update-selected-paymentline',
+ });
+ onChangeOrder(this._onPrevOrder, this._onNewOrder);
+ useErrorHandlers();
+ this.payment_interface = null;
+ this.error = false;
+ this.payment_methods_from_config = this.env.pos.payment_methods.filter(method => this.env.pos.config.payment_method_ids.includes(method.id));
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ get paymentLines() {
+ return this.currentOrder.get_paymentlines();
+ }
+ get selectedPaymentLine() {
+ return this.currentOrder.selected_paymentline;
+ }
+ async selectClient() {
+ // IMPROVEMENT: This code snippet is repeated multiple times.
+ // Maybe it's better to create a function for it.
+ const currentClient = this.currentOrder.get_client();
+ const { confirmed, payload: newClient } = await this.showTempScreen(
+ 'ClientListScreen',
+ { client: currentClient }
+ );
+ if (confirmed) {
+ this.currentOrder.set_client(newClient);
+ this.currentOrder.updatePricelist(newClient);
+ }
+ }
+ addNewPaymentLine({ detail: paymentMethod }) {
+ // original function: click_paymentmethods
+ if (this.currentOrder.electronic_payment_in_progress()) {
+ this.showPopup('ErrorPopup', {
+ title: this.env._t('Error'),
+ body: this.env._t('There is already an electronic payment in progress.'),
+ });
+ return false;
+ } else {
+ this.currentOrder.add_paymentline(paymentMethod);
+ NumberBuffer.reset();
+ this.payment_interface = paymentMethod.payment_terminal;
+ if (this.payment_interface) {
+ this.currentOrder.selected_paymentline.set_payment_status('pending');
+ }
+ return true;
+ }
+ }
+ _updateSelectedPaymentline() {
+ if (this.paymentLines.every((line) => line.paid)) {
+ this.currentOrder.add_paymentline(this.payment_methods_from_config[0]);
+ }
+ if (!this.selectedPaymentLine) return; // do nothing if no selected payment line
+ // disable changing amount on paymentlines with running or done payments on a payment terminal
+ if (
+ this.payment_interface &&
+ !['pending', 'retry'].includes(this.selectedPaymentLine.get_payment_status())
+ ) {
+ return;
+ }
+ if (NumberBuffer.get() === null) {
+ this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } });
+ } else {
+ this.selectedPaymentLine.set_amount(NumberBuffer.getFloat());
+ }
+ }
+ toggleIsToInvoice() {
+ // click_invoice
+ this.currentOrder.set_to_invoice(!this.currentOrder.is_to_invoice());
+ this.render();
+ }
+ openCashbox() {
+ this.env.pos.proxy.printer.open_cashbox();
+ }
+ async addTip() {
+ // click_tip
+ const tip = this.currentOrder.get_tip();
+ const change = this.currentOrder.get_change();
+ let value = tip.toFixed(this.env.pos.decimals);
+
+ if (tip === 0 && change > 0) {
+ value = change;
+ }
+
+ const { confirmed, payload } = await this.showPopup('NumberPopup', {
+ title: tip ? this.env._t('Change Tip') : this.env._t('Add Tip'),
+ startingValue: value,
+ });
+
+ if (confirmed) {
+ this.currentOrder.set_tip(parse.float(payload));
+ }
+ }
+ deletePaymentLine(event) {
+ const { cid } = event.detail;
+ const line = this.paymentLines.find((line) => line.cid === cid);
+
+ // If a paymentline with a payment terminal linked to
+ // it is removed, the terminal should get a cancel
+ // request.
+ if (['waiting', 'waitingCard', 'timeout'].includes(line.get_payment_status())) {
+ line.payment_method.payment_terminal.send_payment_cancel(this.currentOrder, cid);
+ }
+
+ this.currentOrder.remove_paymentline(line);
+ NumberBuffer.reset();
+ this.render();
+ }
+ selectPaymentLine(event) {
+ const { cid } = event.detail;
+ const line = this.paymentLines.find((line) => line.cid === cid);
+ this.currentOrder.select_paymentline(line);
+ NumberBuffer.reset();
+ this.render();
+ }
+ async validateOrder(isForceValidate) {
+ if(this.env.pos.config.cash_rounding) {
+ if(!this.env.pos.get_order().check_paymentlines_rounding()) {
+ this.showPopup('ErrorPopup', {
+ title: this.env._t('Rounding error in payment lines'),
+ body: this.env._t("The amount of your payment lines must be rounded to validate the transaction."),
+ });
+ return;
+ }
+ }
+ if (await this._isOrderValid(isForceValidate)) {
+ // remove pending payments before finalizing the validation
+ for (let line of this.paymentLines) {
+ if (!line.is_done()) this.currentOrder.remove_paymentline(line);
+ }
+ await this._finalizeValidation();
+ }
+ }
+ async _finalizeValidation() {
+ if ((this.currentOrder.is_paid_with_cash() || this.currentOrder.get_change()) && this.env.pos.config.iface_cashdrawer) {
+ this.env.pos.proxy.printer.open_cashbox();
+ }
+
+ this.currentOrder.initialize_validation_date();
+ this.currentOrder.finalized = true;
+
+ let syncedOrderBackendIds = [];
+
+ try {
+ if (this.currentOrder.is_to_invoice()) {
+ syncedOrderBackendIds = await this.env.pos.push_and_invoice_order(
+ this.currentOrder
+ );
+ } else {
+ syncedOrderBackendIds = await this.env.pos.push_single_order(this.currentOrder);
+ }
+ } catch (error) {
+ if (error.code == 700)
+ this.error = true;
+ if (error instanceof Error) {
+ throw error;
+ } else {
+ await this._handlePushOrderError(error);
+ }
+ }
+ if (syncedOrderBackendIds.length && this.currentOrder.wait_for_push_order()) {
+ const result = await this._postPushOrderResolve(
+ this.currentOrder,
+ syncedOrderBackendIds
+ );
+ if (!result) {
+ await this.showPopup('ErrorPopup', {
+ title: 'Error: no internet connection.',
+ body: error,
+ });
+ }
+ }
+
+ this.showScreen(this.nextScreen);
+
+ // If we succeeded in syncing the current order, and
+ // there are still other orders that are left unsynced,
+ // we ask the user if he is willing to wait and sync them.
+ if (syncedOrderBackendIds.length && this.env.pos.db.get_orders().length) {
+ const { confirmed } = await this.showPopup('ConfirmPopup', {
+ title: this.env._t('Remaining unsynced orders'),
+ body: this.env._t(
+ 'There are unsynced orders. Do you want to sync these orders?'
+ ),
+ });
+ if (confirmed) {
+ // NOTE: Not yet sure if this should be awaited or not.
+ // If awaited, some operations like changing screen
+ // might not work.
+ this.env.pos.push_orders();
+ }
+ }
+ }
+ get nextScreen() {
+ return !this.error? 'ReceiptScreen' : 'ProductScreen';
+ }
+ async _isOrderValid(isForceValidate) {
+ if (this.currentOrder.get_orderlines().length === 0) {
+ this.showPopup('ErrorPopup', {
+ title: this.env._t('Empty Order'),
+ body: this.env._t(
+ 'There must be at least one product in your order before it can be validated'
+ ),
+ });
+ return false;
+ }
+
+ if (this.currentOrder.is_to_invoice() && !this.currentOrder.get_client()) {
+ const { confirmed } = await this.showPopup('ConfirmPopup', {
+ title: this.env._t('Please select the Customer'),
+ body: this.env._t(
+ 'You need to select the customer before you can invoice an order.'
+ ),
+ });
+ if (confirmed) {
+ this.selectClient();
+ }
+ return false;
+ }
+
+ if (!this.currentOrder.is_paid() || this.invoicing) {
+ return false;
+ }
+
+ if (this.currentOrder.has_not_valid_rounding()) {
+ var line = this.currentOrder.has_not_valid_rounding();
+ this.showPopup('ErrorPopup', {
+ title: this.env._t('Incorrect rounding'),
+ body: this.env._t(
+ 'You have to round your payments lines.' + line.amount + ' is not rounded.'
+ ),
+ });
+ return false;
+ }
+
+ // The exact amount must be paid if there is no cash payment method defined.
+ if (
+ Math.abs(
+ this.currentOrder.get_total_with_tax() - this.currentOrder.get_total_paid() + this.currentOrder.get_rounding_applied()
+ ) > 0.00001
+ ) {
+ var cash = false;
+ for (var i = 0; i < this.env.pos.payment_methods.length; i++) {
+ cash = cash || this.env.pos.payment_methods[i].is_cash_count;
+ }
+ if (!cash) {
+ this.showPopup('ErrorPopup', {
+ title: this.env._t('Cannot return change without a cash payment method'),
+ body: this.env._t(
+ 'There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'
+ ),
+ });
+ return false;
+ }
+ }
+
+ // if the change is too large, it's probably an input error, make the user confirm.
+ if (
+ !isForceValidate &&
+ this.currentOrder.get_total_with_tax() > 0 &&
+ this.currentOrder.get_total_with_tax() * 1000 < this.currentOrder.get_total_paid()
+ ) {
+ this.showPopup('ConfirmPopup', {
+ title: this.env._t('Please Confirm Large Amount'),
+ body:
+ this.env._t('Are you sure that the customer wants to pay') +
+ ' ' +
+ this.env.pos.format_currency(this.currentOrder.get_total_paid()) +
+ ' ' +
+ this.env._t('for an order of') +
+ ' ' +
+ this.env.pos.format_currency(this.currentOrder.get_total_with_tax()) +
+ ' ' +
+ this.env._t('? Clicking "Confirm" will validate the payment.'),
+ }).then(({ confirmed }) => {
+ if (confirmed) this.validateOrder(true);
+ });
+ return false;
+ }
+
+ return true;
+ }
+ async _postPushOrderResolve(order, order_server_ids) {
+ return true;
+ }
+ async _sendPaymentRequest({ detail: line }) {
+ // Other payment lines can not be reversed anymore
+ this.paymentLines.forEach(function (line) {
+ line.can_be_reversed = false;
+ });
+
+ const payment_terminal = line.payment_method.payment_terminal;
+ line.set_payment_status('waiting');
+
+ const isPaymentSuccessful = await payment_terminal.send_payment_request(line.cid);
+ if (isPaymentSuccessful) {
+ line.set_payment_status('done');
+ line.can_be_reversed = this.payment_interface.supports_reversals;
+ } else {
+ line.set_payment_status('retry');
+ }
+ }
+ async _sendPaymentCancel({ detail: line }) {
+ const payment_terminal = line.payment_method.payment_terminal;
+ line.set_payment_status('waitingCancel');
+ const isCancelSuccessful = await payment_terminal.send_payment_cancel(this.currentOrder, line.cid);
+ if (isCancelSuccessful) {
+ line.set_payment_status('retry');
+ } else {
+ line.set_payment_status('waitingCard');
+ }
+ }
+ async _sendPaymentReverse({ detail: line }) {
+ const payment_terminal = line.payment_method.payment_terminal;
+ line.set_payment_status('reversing');
+
+ const isReversalSuccessful = await payment_terminal.send_payment_reversal(line.cid);
+ if (isReversalSuccessful) {
+ line.set_amount(0);
+ line.set_payment_status('reversed');
+ } else {
+ line.can_be_reversed = false;
+ line.set_payment_status('done');
+ }
+ }
+ async _sendForceDone({ detail: line }) {
+ line.set_payment_status('done');
+ }
+ _onPrevOrder(prevOrder) {
+ prevOrder.off('change', null, this);
+ prevOrder.paymentlines.off('change', null, this);
+ if (prevOrder) {
+ prevOrder.stop_electronic_payment();
+ }
+ }
+ async _onNewOrder(newOrder) {
+ newOrder.on('change', this.render, this);
+ newOrder.paymentlines.on('change', this.render, this);
+ NumberBuffer.reset();
+ await this.render();
+ }
+ }
+ PaymentScreen.template = 'PaymentScreen';
+
+ Registries.Component.add(PaymentScreen);
+
+ return PaymentScreen;
+});
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenElectronicPayment.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenElectronicPayment.js
new file mode 100644
index 00000000..6cafac15
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenElectronicPayment.js
@@ -0,0 +1,23 @@
+odoo.define('point_of_sale.PaymentScreenElectronicPayment', function (require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class PaymentScreenElectronicPayment extends PosComponent {
+ mounted() {
+ this.props.line.on('change', this.render, this);
+ }
+ willUnmount() {
+ if (this.props.line) {
+ // It could be that the line is deleted before unmounting the element.
+ this.props.line.off('change', null, this);
+ }
+ }
+ }
+ PaymentScreenElectronicPayment.template = 'PaymentScreenElectronicPayment';
+
+ Registries.Component.add(PaymentScreenElectronicPayment);
+
+ return PaymentScreenElectronicPayment;
+});
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js
new file mode 100644
index 00000000..e661722f
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js
@@ -0,0 +1,18 @@
+odoo.define('point_of_sale.PaymentScreenNumpad', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class PaymentScreenNumpad extends PosComponent {
+ constructor() {
+ super(...arguments);
+ this.decimalPoint = this.env._t.database.parameters.decimal_point;
+ }
+ }
+ PaymentScreenNumpad.template = 'PaymentScreenNumpad';
+
+ Registries.Component.add(PaymentScreenNumpad);
+
+ return PaymentScreenNumpad;
+});
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js
new file mode 100644
index 00000000..8f231146
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js
@@ -0,0 +1,23 @@
+odoo.define('point_of_sale.PaymentScreenPaymentLines', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class PaymentScreenPaymentLines extends PosComponent {
+ formatLineAmount(paymentline) {
+ return this.env.pos.format_currency_no_symbol(paymentline.get_amount());
+ }
+ selectedLineClass(line) {
+ return { 'payment-terminal': line.get_payment_status() };
+ }
+ unselectedLineClass(line) {
+ return {};
+ }
+ }
+ PaymentScreenPaymentLines.template = 'PaymentScreenPaymentLines';
+
+ Registries.Component.add(PaymentScreenPaymentLines);
+
+ return PaymentScreenPaymentLines;
+});
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js
new file mode 100644
index 00000000..12ccaa84
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js
@@ -0,0 +1,30 @@
+odoo.define('point_of_sale.PaymentScreenStatus', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class PaymentScreenStatus extends PosComponent {
+ get changeText() {
+ return this.env.pos.format_currency(this.currentOrder.get_change());
+ }
+ get totalDueText() {
+ return this.env.pos.format_currency(
+ this.currentOrder.get_total_with_tax() + this.currentOrder.get_rounding_applied()
+ );
+ }
+ get remainingText() {
+ return this.env.pos.format_currency(
+ this.currentOrder.get_due() > 0 ? this.currentOrder.get_due() : 0
+ );
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ }
+ PaymentScreenStatus.template = 'PaymentScreenStatus';
+
+ Registries.Component.add(PaymentScreenStatus);
+
+ return PaymentScreenStatus;
+});