From 7202e9f65845c602878381c167a870fea54b83a5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 13 Nov 2020 17:33:20 +0530 Subject: [PATCH] refactor: pos payment with new ui --- erpnext/public/scss/point-of-sale.scss | 199 ++++++++++++++++-- .../page/point_of_sale/pos_controller.js | 6 +- .../page/point_of_sale/pos_item_cart.js | 57 +++-- .../page/point_of_sale/pos_item_selector.js | 2 +- .../selling/page/point_of_sale/pos_payment.js | 162 +++++++------- 5 files changed, 297 insertions(+), 129 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 573b9dc2b7..61be422e48 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -5,12 +5,12 @@ section { min-height: 45rem; + height: calc(100vh - 200px); max-height: calc(100vh - 200px); } .frappe-control { margin: 0 !important; - padding: 5px 5px; width: 100%; } @@ -63,9 +63,7 @@ align-items: center; justify-content: center; padding: var(--padding-sm); - margin: var(--margin-xs); margin-top: var(--margin-sm); - margin-bottom: var(--margin-xs); border-radius: var(--border-radius-md); font-size: var(--text-lg); font-weight: 700; @@ -91,21 +89,21 @@ position: sticky; top: -1px; z-index: 1; - padding: var(--padding-md); + padding: var(--padding-lg); padding-bottom: var(--padding-sm); align-items: center; > .label { @extend .label; grid-column: span 4 / span 4; - padding: var(--padding-xs); - padding-top: 0px; + padding-bottom: var(--padding-xs); } > .search-field { grid-column: span 5 / span 5; display: flex; align-items: center; + margin-right: var(--padding-sm); } > .item-group-field { @@ -177,7 +175,7 @@ @extend .pos-card; display: flex; flex-direction: column; - padding: var(--padding-md); + padding: var(--padding-md) var(--padding-lg); > .customer-field { display: flex; @@ -288,36 +286,33 @@ position: absolute; display: flex; flex-direction: column; - padding: var(--padding-md); + padding: var(--padding-lg); width: 100%; height: 100%; > .cart-label { @extend .label; padding-bottom: var(--padding-md); - padding-left: var(--margin-sm); } > .cart-header { display: flex; width: 100%; font-size: var(--text-md); - padding-left: var(--padding-xs); - padding-right: var(--padding-xs); padding-bottom: var(--padding-md); > .name-header { flex: 1 1 0%; - margin-left: var(--margin-xs); } > .qty-header { margin-right: var(--margin-lg); + text-align: center; } > .rate-amount-header { - margin-right: var(--margin-xs); text-align: right; + margin-right: var(--margin-sm); } } @@ -429,11 +424,38 @@ width: 100%; margin-top: var(--margin-md); + > .add-discount-wrapper { + @extend .pointer-no-select; + display: none; + align-items: center; + border-radius: var(--border-radius-md); + border: 1px dashed var(--gray-500); + padding: var(--padding-sm) var(--padding-md); + margin-bottom: var(--margin-sm); + + > .add-discount-field { + width: 100%; + } + + > .discount-icon { + margin-right: var(--margin-sm); + } + + > .edit-discount-btn { + padding: 3px var(--padding-sm); + border-radius: var(--border-radius-sm); + background-color: var(--green-100); + color: var(--green-700); + font-size: var(--text-sm); + font-weight: 700; + } + } + > .net-total-container { display: flex; align-items: center; justify-content: space-between; - padding: var(--padding-sm); + padding: var(--padding-sm) 0px; font-weight: 500; font-size: var(--text-md); } @@ -442,7 +464,7 @@ display: none; align-items: center; justify-content: space-between; - padding: var(--padding-sm); + padding: var(--padding-sm) 0px; font-weight: 500; font-size: var(--text-md); @@ -460,7 +482,7 @@ display: flex; align-items: center; justify-content: space-between; - padding: var(--padding-sm); + padding: var(--padding-sm) 0px; font-weight: 700; font-size: var(--text-lg); } @@ -474,8 +496,15 @@ > .edit-cart-btn { @extend .primary-action; display: none; - background-color: var(--gray-100); + background-color: var(--gray-300); font-weight: 500; + transition: all 0.15s ease-in-out; + + &:hover { + background-color: var(--gray-600); + color: white; + font-weight: 700; + } } } @@ -682,12 +711,144 @@ > .form-container { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - column-gap: var(--padding-xs); + column-gap: var(--padding-md); > .auto-fetch-btn { @extend .pointer-no-select; - margin: auto var(--margin-xs); + margin: var(--margin-xs); } } } + + > .payment-container { + @extend .pos-card; + grid-column: span 6 / span 6; + display: none; + flex-direction: column; + padding: var(--padding-lg); + + .border-primary { + border: 1px solid var(--blue-500); + } + + .submit-order-btn { + @extend .primary-action; + background-color: var(--blue-500); + color: white; + } + + .section-label { + @extend .label; + @extend .pointer-no-select; + margin-bottom: var(--margin-md); + } + + > .payment-modes { + display: flex; + margin-bottom: var(--margin-md); + overflow-x: scroll; + overflow-y: hidden; + + > .payment-mode-wrapper { + min-width: 40%; + padding: var(--padding-xs); + + > .mode-of-payment { + @extend .pos-card; + @extend .pointer-no-select; + padding: var(--padding-md) var(--padding-lg); + + > .pay-amount { + display: inline; + float: right; + font-weight: 700; + } + + > .mode-of-payment-control { + display: none; + align-items: center; + margin-top: var(--margin-sm); + margin-bottom: var(--margin-xs); + } + + > .loyalty-amount-name { + display: none; + float: right; + font-weight: 700; + } + + > .cash-shortcuts { + display: none; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--margin-sm); + font-size: var(--text-sm); + text-align: center; + + > .shortcut { + @extend .pointer-no-select; + border-radius: var(--border-radius-sm); + background-color: var(--gray-100); + font-weight: 500; + padding: var(--padding-xs) var(--padding-sm); + transition: all 0.15s ease-in-out; + + &:hover { + background-color: var(--gray-300); + } + } + } + } + } + } + + .invoice-fields { + display: none; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: var(--padding-md); + } + + > .totals-section { + display: flex; + margin-top: auto; + margin-bottom: var(--margin-sm); + justify-content: center; + flex-direction: column; + + > .totals { + display: flex; + padding-top: var(--padding-md); + background-color: var(--gray-100); + justify-content: center; + padding: var(--padding-md); + border-radius: var(--border-radius-md); + + > .col { + flex-grow: 1; + text-align: center; + + > .total-label { + font-size: var(--text-md); + font-weight: 500; + color: var(--gray-600); + } + + > .value { + font-size: var(--text-2xl); + font-weight: 700; + } + } + + > .seperator-y { + margin-left: var(--margin-sm); + margin-right: var(--margin-sm); + border-right: 1px solid var(--gray-300); + } + } + + > .number-pad { + display: none; + } + } + + } } \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 8cbd59086c..4875ec061f 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -356,10 +356,10 @@ erpnext.PointOfSale.Controller = class { toggle_other_sections: (show) => { if (show) { - this.item_details.$component.hasClass('d-none') ? '' : this.item_details.$component.addClass('d-none'); - this.item_selector.$component.addClass('d-none'); + this.item_details.$component.is(':visible') ? this.item_details.$component.css('display', 'none') : ''; + this.item_selector.$component.css('display', 'none'); } else { - this.item_selector.$component.removeClass('d-none'); + this.item_selector.$component.css('display', 'flex'); } }, diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index b560ba4a87..867cd2df63 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -78,12 +78,23 @@ erpnext.PointOfSale.ItemCart = class { ); } + get_discount_icon() { + return ( + ` + + + + + ` + ); + } + make_cart_totals_section() { this.$totals_section = this.$component.find('.cart-totals-section'); this.$totals_section.append( - `
- + Add Discount + `
+ ${this.get_discount_icon()} Add Discount
Net Total
@@ -98,7 +109,7 @@ erpnext.PointOfSale.ItemCart = class {
Edit Cart
` ) - this.$add_discount_elem = this.$component.find(".add-discount"); + this.$add_discount_elem = this.$component.find(".add-discount-wrapper"); } make_cart_numpad() { @@ -178,19 +189,15 @@ erpnext.PointOfSale.ItemCart = class { me.events.checkout(); me.toggle_checkout_btn(false); - - me.$add_discount_elem.removeClass("d-none"); }); this.$totals_section.on('click', '.edit-cart-btn', () => { this.events.edit_cart(); this.toggle_checkout_btn(true); - - this.$add_discount_elem.addClass("d-none"); }); - this.$component.on('click', '.add-discount', () => { - const can_edit_discount = this.$add_discount_elem.find('.edit-discount').length; + this.$component.on('click', '.add-discount-wrapper', () => { + const can_edit_discount = this.$add_discount_elem.find('.edit-discount-btn').length; if(!this.discount_field || can_edit_discount) this.show_discount_control(); }); @@ -244,10 +251,10 @@ erpnext.PointOfSale.ItemCart = class { this.$component.find(".edit-cart-btn").click() } }); - this.$component.find(".add-discount").attr("title", `${ctrl_label}+D`); + this.$component.find(".add-discount-wrapper").attr("title", `${ctrl_label}+D`); frappe.ui.keys.add_shortcut({ shortcut: "ctrl+d", - action: () => this.$component.find(".add-discount").click(), + action: () => this.$component.find(".add-discount-wrapper").click(), condition: () => this.$add_discount_elem.is(":visible"), description: __("Add Order Discount"), ignore_inputs: true, @@ -292,7 +299,6 @@ erpnext.PointOfSale.ItemCart = class { label: __('Customer'), fieldtype: 'Link', options: 'Customer', - input_class: 'input-xs', placeholder: __('Search by customer name, phone, email.'), get_query: () => query, onchange: function() { @@ -351,9 +357,9 @@ erpnext.PointOfSale.ItemCart = class { } show_discount_control() { - this.$add_discount_elem.removeClass("pr-4 pl-4"); + this.$add_discount_elem.css({ 'padding': '0px', 'border': 'none' }) this.$add_discount_elem.html( - `
` + `
` ); const me = this; @@ -362,14 +368,19 @@ erpnext.PointOfSale.ItemCart = class { label: __('Discount'), fieldtype: 'Data', placeholder: __('Enter discount percentage.'), + input_class: 'input-xs', onchange: function() { const frm = me.events.get_frm(); - if (this.value.length || this.value === 0) { + if (flt(this.value) != 0) { frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value)); me.hide_discount_control(this.value); } else { frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', 0); - me.$add_discount_elem.html(`+ Add Discount`); + me.$add_discount_elem.css({ + 'border': '1px dashed var(--gray-500)', + 'padding': 'var(--padding-sm) var(--padding-md)' + }); + me.$add_discount_elem.html(`${me.get_discount_icon()} Add Discount`); me.discount_field = undefined; } }, @@ -383,21 +394,19 @@ erpnext.PointOfSale.ItemCart = class { hide_discount_control(discount) { if (!discount) { - this.$add_discount_elem.removeClass("pr-4 pl-4"); + this.$add_discount_elem.css({ 'padding': '0px', 'border': 'none' }); this.$add_discount_elem.html( - `
` + `
` ); } else { - this.$add_discount_elem.addClass('pr-4 pl-4'); this.$add_discount_elem.html( - ` -
+
${String(discount).bold()}% off -
- ` +
` ); } } @@ -652,10 +661,12 @@ erpnext.PointOfSale.ItemCart = class { highlight_checkout_btn(toggle) { if (toggle) { + this.$add_discount_elem.css('display', 'flex'); this.$cart_container.find('.checkout-btn').css({ 'background-color': 'var(--blue-500)' }); } else { + this.$add_discount_elem.css('display', 'none'); this.$cart_container.find('.checkout-btn').css({ 'background-color': 'var(--blue-200)' }); diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 0bac84481d..e2a2365e32 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -261,6 +261,6 @@ erpnext.PointOfSale.ItemSelector = class { } toggle_component(show) { - show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } } \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index e4d8965ac2..9f8c2dff1d 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -18,38 +18,28 @@ erpnext.PointOfSale.Payment = class { prepare_dom() { this.wrapper.append( - `
-
-
- PAYMENT METHOD -
-
-
-
-
-
-
-
-
-
-
-
-
- Complete Order -
-
-
-
+ `
+ +
+
+
+
+
+
+
Complete Order
` ) - this.$component = this.wrapper.find('.payment-section'); + this.$component = this.wrapper.find('.payment-container'); this.$payment_modes = this.$component.find('.payment-modes'); - this.$totals_remarks = this.$component.find('.totals-remarks'); + this.$totals_section = this.$component.find('.totals-section'); this.$totals = this.$component.find('.totals'); - this.$remarks = this.$component.find('.remarks'); this.$numpad = this.$component.find('.number-pad'); - this.$invoice_details_section = this.$component.find('.invoice-details-section'); + this.$invoice_fields_section = this.$component.find('.invoice-fields-section'); } make_invoice_fields_control() { @@ -57,13 +47,16 @@ erpnext.PointOfSale.Payment = class { const fields = doc.invoice_fields; if (!fields.length) return; - this.$invoice_details_section.html( - `
- ADDITIONAL INFORMATION + this.$invoice_fields_section.html( + ` -
` +
` ); - this.$invoice_fields = this.$invoice_details_section.find('.invoice-fields'); + this.$invoice_fields = this.$invoice_fields_section.find('.invoice-fields'); const frm = this.events.get_frm(); fields.forEach(df => { @@ -127,9 +120,9 @@ erpnext.PointOfSale.Payment = class { this.selected_mode.set_value(this.numpad_value); function highlight_numpad_btn($btn) { - $btn.addClass('shadow-inner bg-selected'); + $btn.addClass('shadow-base-inner bg-selected'); setTimeout(() => { - $btn.removeClass('shadow-inner bg-selected'); + $btn.removeClass('shadow-base-inner bg-selected'); }, 100); } } @@ -142,13 +135,16 @@ erpnext.PointOfSale.Payment = class { // if clicked element doesn't have .mode-of-payment class then return if (!$(e.target).is(mode_clicked)) return; + // const scrollRight = mode_clicked.offset().right - me.$payment_modes.offset().right + me.$payment_modes.scrollRight(); + // me.$payment_modes.animate({ scrollRight }); + const mode = mode_clicked.attr('data-mode'); // hide all control fields and shortcuts - $(`.mode-of-payment-control`).addClass('d-none'); - $(`.cash-shortcuts`).addClass('d-none'); - me.$payment_modes.find(`.pay-amount`).removeClass('d-none'); - me.$payment_modes.find(`.loyalty-amount-name`).addClass('d-none'); + $(`.mode-of-payment-control`).css('display', 'none'); + $(`.cash-shortcuts`).css('display', 'none'); + me.$payment_modes.find(`.pay-amount`).css('display', 'inline'); + me.$payment_modes.find(`.loyalty-amount-name`).css('display', 'none'); // remove highlight from all mode-of-payments $('.mode-of-payment').removeClass('border-primary'); @@ -161,10 +157,10 @@ erpnext.PointOfSale.Payment = class { } else { // clicked one is not selected then select it mode_clicked.addClass('border-primary'); - mode_clicked.find('.mode-of-payment-control').removeClass('d-none'); - mode_clicked.find('.cash-shortcuts').removeClass('d-none'); - me.$payment_modes.find(`.${mode}-amount`).addClass('d-none'); - me.$payment_modes.find(`.${mode}-name`).removeClass('d-none'); + mode_clicked.find('.mode-of-payment-control').css('display', 'flex'); + mode_clicked.find('.cash-shortcuts').css('display', 'grid'); + me.$payment_modes.find(`.${mode}-amount`).css('display', 'none'); + me.$payment_modes.find(`.${mode}-name`).css('display', 'inline'); me.toggle_numpad(true); me.selected_mode = me[`${mode}_control`]; @@ -198,7 +194,7 @@ erpnext.PointOfSale.Payment = class { me.selected_mode.set_value(value); }) - this.$component.on('click', '.submit-order', () => { + this.$component.on('click', '.submit-order-btn', () => { const doc = this.events.get_frm().doc; const paid_amount = doc.paid_amount; const items = doc.items; @@ -217,9 +213,9 @@ erpnext.PointOfSale.Payment = class { this.update_totals_section(frm.doc); // need to re calculate cash shortcuts after discount is applied - const is_cash_shortcuts_invisible = this.$payment_modes.find('.cash-shortcuts').hasClass('d-none'); + const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible'); this.attach_cash_shortcuts(frm.doc); - !is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').removeClass('d-none'); + !is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid'); }) frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => { @@ -236,28 +232,28 @@ erpnext.PointOfSale.Payment = class { } }); - this.$component.on('click', '.invoice-details-section', function(e) { + this.$component.on('click', '.invoice-fields-section', function(e) { if ($(e.target).closest('.invoice-fields').length) return; - me.$payment_modes.addClass('d-none'); - me.$invoice_fields.toggleClass("d-none"); + me.$payment_modes.css('display', 'none'); + me.$invoice_fields.css('display', 'grid'); me.toggle_numpad(false); }); this.$component.on('click', '.payment-section', () => { - this.$invoice_fields.addClass("d-none"); - this.$payment_modes.toggleClass('d-none'); + this.$invoice_fields.css('display', 'none'); + this.$payment_modes.css('display', 'flex'); this.toggle_numpad(true); }) } attach_shortcuts() { const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; - this.$component.find('.submit-order').attr("title", `${ctrl_label}+Enter`); + this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`); frappe.ui.keys.on("ctrl+enter", () => { const payment_is_visible = this.$component.is(":visible"); const active_mode = this.$payment_modes.find(".border-primary"); if (payment_is_visible && active_mode.length) { - this.$component.find('.submit-order').click(); + this.$component.find('.submit-order-btn').click(); } }); @@ -287,15 +283,13 @@ erpnext.PointOfSale.Payment = class { } toggle_numpad(show) { - if (show) { - this.$numpad.removeClass('d-none'); - this.$remarks.addClass('d-none'); - this.$totals_remarks.addClass('w-60 justify-center').removeClass('justify-end w-full'); - } else { - this.$numpad.addClass('d-none'); - this.$remarks.removeClass('d-none'); - this.$totals_remarks.removeClass('w-60 justify-center').addClass('justify-end w-full'); - } + // if (show) { + // this.$numpad.css('display', 'flex'); + // this.$totals_section.addClass('w-60 justify-center').removeClass('justify-end w-full'); + // } else { + // this.$numpad.css('display', 'none'); + // this.$totals_section.removeClass('w-60 justify-center').addClass('justify-end w-full'); + // } } render_payment_section() { @@ -327,7 +321,7 @@ erpnext.PointOfSale.Payment = class { fieldtype: 'Data', onchange: function() {} }, - parent: this.$totals_remarks.find(`.remarks`), + parent: this.$totals_section.find(`.remarks`), render_input: true, }); this[`remark_control`].set_value(''); @@ -348,12 +342,11 @@ erpnext.PointOfSale.Payment = class { const amount = p.amount > 0 ? format_currency(p.amount, currency) : ''; return ( - `
-
+ `
+
${p.mode_of_payment} -
${amount}
-
+
${amount}
+
` ) @@ -405,12 +398,10 @@ erpnext.PointOfSale.Payment = class { this.$payment_modes.find('.cash-shortcuts').remove(); this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after( - `
+ `
${ shortcuts.map(s => { - return `
- ${format_currency(s, currency, 0)} -
` + return `
${format_currency(s, currency, 0)}
` }).join('') }
` @@ -457,13 +448,12 @@ erpnext.PointOfSale.Payment = class { const margin = this.$payment_modes.children().length % 2 === 0 ? 'pr-2' : 'pl-2'; const amount = doc.loyalty_amount > 0 ? format_currency(doc.loyalty_amount, doc.currency) : ''; this.$payment_modes.append( - `
-
+ `
+
Redeem Loyalty Points -
${amount}
-
${loyalty_program}
-
+
${amount}
+
${loyalty_program}
+
` ) @@ -520,18 +510,24 @@ erpnext.PointOfSale.Payment = class { const label = change ? __('Change') : __('To Be Paid'); this.$totals.html( - `
-
Paid Amount
-
${format_currency(paid_amount, currency)}
+ `
+
Grand Total
+
${format_currency(doc.grand_total, currency)}
-
-
${label}
-
${format_currency(change || remaining, currency)}
+
+
+
Paid Amount
+
${format_currency(paid_amount, currency)}
+
+
+
+
${label}
+
${format_currency(change || remaining, currency)}
` ) } toggle_component(show) { - show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } } \ No newline at end of file