refactor: POS workflow (#20789)

* refactor: add pos invoice doctype replacing sales invoice in POS

* refactor: move pos.py to pos invoice

* feat: add pos invoice merge log doctype

* feat: ability to merge pos invoices into a sales invoice

* feat: [wip] new ui for point of sale

* fix: pos.py moved to pos_invoice

* feat: loyalty points for POS Invoice

* fix: loyalty points on merging

* feat: return against pos invoices

* Merge 'fork/serial-no-selector' into refactor-pos-invoice

* chore: status fix and set warehouse from pos profile

* fix: naming series

* feat: merge pos returns into credit notes

* feat: add pos list action for merging into sales invoices

* feat[UX]: add shortcuts & focus on search after customer selection

* feat: stock validation from previous pos transactions

* Merge 'fork/serial-no-selector' into refactor-pos-invoice

* chore: fix df not found for base_amount precision

* feat: serial no validation from previous pos transactions

* chore: move pos.py into pos page

* feat: pos opening voucher

* feat: link pos closing voucher with opening voucher

* chore: use map_doc instead of get_mapped_doc for better perf

* feat: enforce opening voucher on pos page

* feat: [ui] [wip] point of sale beta ui refactor

* fix: auto fetching serial nos with batch no

* feat: [ui] item details section for new pos ui

* feat: remove item from cart

* refactor: [ui] [wip] split point_of_sale into components
* new payment component
* new numberpad
* fix pos opening status
* move from flex to grids

* fix: search from item selector

* feat: loyalty points as payment method

* feat: pos invoice status
* fix a bug with invalid JSON

* fix: loyalty program ui fixes

* feat: past order list and past order summary

* feat: (minor) setting discount from item details

* fix: adding item before customer selection

* feat: post order submission summary
* save and open draft orders
* fix: item group filter

* fix:  item_det not defined while submitting sle

* fix: minor bugs

* fix: minor ux fixes

* feat: show opening time in pos ui

* feat: item and customer images

* feat: emailing and printing an invoice

* fix: item details field edit shows empty alert

* fix: (minor) ux fixes

* chore: rename pos opening voucher to pos opening entry

* chore: (minor) rename pos closing voucher and sub doctypes

* chore: add patch for renaming pos closing doctypes

* fix: negative stock not allowed in pos invoices* default is_pos in pos invoices* fix: transalation

* fix: invoices not getting fetched on pos closing

* fix: indentation

* feat: view / edit customer info

* fix: minor bugs

* fix: minor bug

* fix: patch

* fix: minor ux issues

* fix: remove uppercase status

* refactor: pos closing payment reconciliation

* fix: move pos invoice print formats to pos invoice doctype

* fix: ui issues

* feat: new child doctype to store pos payment mode details

* fix: add to patches.txt

* feat: search by serial no

* chore: [wip] code cleanup

* fix: item not selectable from cart

* chore: [wip] code cleanup

* fix: minor issues
* loyalty points transactions
* default payment mode

* fix: minor fixes
* set correct mop amount with loaylty points
* editing draft invoices from UI

* chore: pos invoice merge log tests

* fix: batch / serial validation in pos ui and on submission

* feat: use onscan js for barcode scan events

* fix: cart header with amount column

* fix: validate batch no and qty in pos transactions

* chore: do not fetch closing balances as opening balance

* feat: show available qty in item selector

* feat: shortcuts

* fix: onscan.js not found

* fix: onscan.js not found

* fix: cannot return partial items

* fix: neagtive stock indicator

* feat: invoice discount

* fix: change available stock on warehouse change

* chore: cleanup code

* fix: pos profile payment method table

* feat: adding same item with different uom

* fix: loyalty points deleted after consolidation

* fix: enter loyalty amount instead of loyalty points

* chore: return print format

* feat: custom fields in pos view

* chore: pos invoice test

* chore: remove offline pos

* fix: cyclic dependency

* fix: cyclic dependency

* patch: remove pos page and order fixes

* chore: little fixes

* fix: patch perf and plural naming

* chore: tidy up pos invoice validation

* chore: move pos closing to accounts

* fix: move pos doctypes to accounts

* fix: move pos doctypes to accounts

* fix: item description in cart

* fix: item description in cart

* chore: loyalty tests
* minor fixes

* chore: rename point of sale beta to point of sale

* chore: reset past order summary on filter change

* chore: add point of sale to accounting desk

* fix: payment reconciliation table in pos closing

* fix: travis

* Update accounting.json

* fix: test cases

* fix: tests
* patch loyalty point entries

* fix: remove test
* default mode of payment is mandatory for pos transaction

* chore: remove unused checks from pos profile

* fix: loyalty point entry patch

* fix: numpad reset and patches

* fix: minor bugs

* fix: travis

* fix: travis

* fix: travis

* fix: travis

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html b/erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html
deleted file mode 100644
index 2412b07..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<div class="clearfix"></div>
-<div class="box">
-		<div class="grid-body">
-			<div class="rows text-center">
-
-				<!-- Sales summary section -->
-				<div>
-						<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Sales Summary") }}</h6>
-						<div class="tax-break-up" style="overflow-x: auto;">
-							<table class="table table-bordered table-hover">
-								<thead>
-								</thead>
-								<tbody>
-									<tr>
-										<td class="text-left">{{ _('Grand Total') }}</td>
-										<td class='text-right'>{{ data.grand_total or '' }} {{ currency.symbol }}</td>
-									</tr>
-									<tr>
-										<td class="text-left">{{ _('Net Total') }}</td>
-										<td class='text-right'>{{ data.net_total or '' }} {{ currency.symbol }}</td>
-									</tr>
-									<tr>
-										<td class="text-left">{{ _('Total Quantity') }}</td>
-										<td class='text-right'>{{ data.total_quantity or '' }}</td>
-									</tr>
-
-							</tbody>
-						</table>
-					</div>
-				</div>
-				<!-- Section end -->
-
-				<!-- Mode of payment section -->
-				<div>
-						<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Mode of Payments") }}</h6>
-						<div class="tax-break-up" style="overflow-x: auto;">
-							<table class="table table-bordered table-hover">
-								<thead>
-									<tr>
-										<th class="text-left">{{ _("Mode of Payment") }}</th>
-										<th class="text-right">{{ _("Amount") }}</th>
-									</tr>
-								</thead>
-								<tbody>
-								{% for d in data.payment_reconciliation %}
-									<tr>
-										<td class="text-left">{{ d.mode_of_payment }}</td>
-										<td class='text-right'>{{ d.expected_amount }} {{ currency.symbol }}</td>
-									</tr>
-								{% endfor %}
-							</tbody>
-						</table>
-					</div>
-				</div>
-				<!-- Section end -->
-
-				<!-- Taxes section -->
-				<div>
-						<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Taxes") }}</h6>
-						<div class="tax-break-up" style="overflow-x: auto;">
-							<table class="table table-bordered table-hover">
-								<thead>
-									<tr>
-										<th class="text-left">{{ _("Rate") }}</th>
-										<th class="text-right">{{ _("Amount") }}</th>
-									</tr>
-								</thead>
-								<tbody>
-								{% for d in data.taxes %}
-									<tr>
-										<td class="text-left">{{ d.rate }} %</td>
-										<td class='text-right'>{{ d.amount }} {{ currency.symbol }}</td>
-									</tr>
-								{% endfor %}
-							</tbody>
-						</table>
-					</div>
-				</div>
-				<!-- Section end -->
-
-			</div>
-		</div>
-	</div>
-</div>
diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js
deleted file mode 100644
index f24caf7..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('POS Closing Voucher', {
-	onload: function(frm) {
-		frm.set_query("pos_profile", function(doc) {
-			return {
-				filters: {
-					'user': doc.user
-				}
-			};
-		});
-
-		frm.set_query("user", function(doc) {
-			return {
-				query: "erpnext.selling.doctype.pos_closing_voucher.pos_closing_voucher.get_cashiers",
-				filters: {
-					'parent': doc.pos_profile
-				}
-			};
-		});
-	},
-
-	total_amount: function(frm) {
-		get_difference_amount(frm);
-	},
-	custody_amount: function(frm){
-		get_difference_amount(frm);
-	},
-	expense_amount: function(frm){
-		get_difference_amount(frm);
-	},
-	refresh: function(frm) {
-		get_closing_voucher_details(frm);
-	},
-	period_start_date: function(frm) {
-		get_closing_voucher_details(frm);
-	},
-	period_end_date: function(frm) {
-		get_closing_voucher_details(frm);
-	},
-	company: function(frm) {
-		get_closing_voucher_details(frm);
-	},
-	pos_profile: function(frm) {
-		get_closing_voucher_details(frm);
-	},
-	user: function(frm) {
-		get_closing_voucher_details(frm);
-	},
-});
-
-frappe.ui.form.on('POS Closing Voucher Details', {
-	collected_amount: function(doc, cdt, cdn) {
-		var row = locals[cdt][cdn];
-		frappe.model.set_value(cdt, cdn, "difference", row.collected_amount - row.expected_amount);
-	}
-});
-
-var get_difference_amount = function(frm){
-	frm.doc.difference = frm.doc.total_amount - frm.doc.custody_amount - frm.doc.expense_amount;
-	refresh_field("difference");
-};
-
-var get_closing_voucher_details = function(frm) {
-	if (frm.doc.period_end_date && frm.doc.period_start_date && frm.doc.company && frm.doc.pos_profile && frm.doc.user) {
-		frappe.call({
-			method: "get_closing_voucher_details",
-			doc: frm.doc,
-			callback: function(r) {
-				if (r.message) {
-					refresh_field("payment_reconciliation");
-					refresh_field("sales_invoices_summary");
-					refresh_field("taxes");
-
-					refresh_field("grand_total");
-					refresh_field("net_total");
-					refresh_field("total_quantity");
-					refresh_field("total_amount");
-
-					frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
-				}
-			}
-		});
-	}
-
-};
diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json
deleted file mode 100644
index 2ac5779..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json
+++ /dev/null
@@ -1,1016 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "POS-CLO-.YYYY.-.#####", 
- "beta": 0, 
- "creation": "2018-05-28 19:06:40.830043", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "Today", 
-   "fieldname": "period_start_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Period Start Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "Today", 
-   "fieldname": "period_end_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Period End Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_3", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "Today", 
-   "fieldname": "posting_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Posting Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_5", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "company", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Company", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_7", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "pos_profile", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "POS Profile", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "POS Profile", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "", 
-   "fieldname": "user", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Cashier", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "User", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "expense_details_section", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Expense Details", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "expense_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Expense Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "custody_amount", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Amount in Custody", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_13", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "total_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Total Collected Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "difference", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Difference", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_9", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "payment_reconciliation_details", 
-   "fieldtype": "HTML", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_11", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Modes of Payment", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "payment_reconciliation", 
-   "fieldtype": "Table", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Payment Reconciliation", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "POS Closing Voucher Details", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 1, 
-   "columns": 0, 
-   "fieldname": "section_break_13", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Details", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "grand_total", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Grand Total", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "net_total", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Net Total", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "total_quantity", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Total Quantity", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_16", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Taxes", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "taxes", 
-   "fieldtype": "Table", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Taxes", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "POS Closing Voucher Taxes", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 1, 
-   "columns": 0, 
-   "fieldname": "section_break_12", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Linked Invoices", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sales_invoices_summary", 
-   "fieldtype": "Table", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Sales Invoices Summary", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "POS Closing Voucher Invoices", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_14", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amended_from", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Amended From", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "POS Closing Voucher", 
-   "permlevel": 0, 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 1, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2019-01-28 12:33:45.217813", 
- "modified_by": "Administrator", 
- "module": "Selling", 
- "name": "POS Closing Voucher", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Sales Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py
deleted file mode 100644
index bb5f83e..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from collections import defaultdict
-from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
-import json
-
-class POSClosingVoucher(Document):
-	def get_closing_voucher_details(self):
-		filters = {
-			'doc': self.name,
-			'from_date': self.period_start_date,
-			'to_date': self.period_end_date,
-			'company': self.company,
-			'pos_profile': self.pos_profile,
-			'user': self.user,
-			'is_pos': 1
-		}
-
-		invoice_list = get_invoices(filters)
-		self.set_invoice_list(invoice_list)
-
-		sales_summary = get_sales_summary(invoice_list)
-		self.set_sales_summary_values(sales_summary)
-		self.total_amount = sales_summary['grand_total']
-
-		if not self.get('payment_reconciliation'):
-			mop = get_mode_of_payment_details(invoice_list)
-			self.set_mode_of_payments(mop)
-
-		taxes = get_tax_details(invoice_list)
-		self.set_taxes(taxes)
-
-		return self.get_payment_reconciliation_details()
-
-	def validate(self):
-		user = frappe.get_all('POS Closing Voucher',
-			filters = {
-				'user': self.user,
-				'docstatus': 1
-			},
-			or_filters = {
-					'period_start_date': ('between', [self.period_start_date, self.period_end_date]),
-					'period_end_date': ('between', [self.period_start_date, self.period_end_date])
-			})
-
-		if user:
-			frappe.throw(_("POS Closing Voucher alreday exists for {0} between date {1} and {2}")
-				.format(self.user, self.period_start_date, self.period_end_date))
-
-	def set_invoice_list(self, invoice_list):
-		self.sales_invoices_summary = []
-		for invoice in invoice_list:
-			self.append('sales_invoices_summary', {
-				'invoice': invoice['name'],
-				'qty_of_items': invoice['pos_total_qty'],
-				'grand_total': invoice['grand_total']
-			})
-
-	def set_sales_summary_values(self, sales_summary):
-		self.grand_total = sales_summary['grand_total']
-		self.net_total = sales_summary['net_total']
-		self.total_quantity = sales_summary['total_qty']
-
-	def set_mode_of_payments(self, mop):
-		self.payment_reconciliation = []
-		for m in mop:
-			self.append('payment_reconciliation', {
-				'mode_of_payment': m['name'],
-				'expected_amount': m['amount']
-			})
-
-	def set_taxes(self, taxes):
-		self.taxes = []
-		for tax in taxes:
-			self.append('taxes', {
-				'rate': tax['rate'],
-				'amount': tax['amount']
-			})
-
-	def get_payment_reconciliation_details(self):
-		currency = get_company_currency(self)
-		return frappe.render_template("erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html",
-			{"data": self, "currency": currency})
-
-@frappe.whitelist()
-def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
-	cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
-	cashiers = [cashier for cashier in set(c['user'] for c in cashiers_list)]
-	return [[c] for c in cashiers]
-
-def get_mode_of_payment_details(invoice_list):
-	mode_of_payment_details = []
-	invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
-	if invoice_list:
-		inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date,
-			ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
-			from `tabSales Invoice` a, `tabSales Invoice Payment` b
-			where a.name = b.parent
-			and a.name in ({invoice_list_names})
-			group by a.owner, a.posting_date, mode_of_payment
-			union
-			select a.owner,a.posting_date,
-			ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_paid_amount) as paid_amount
-			from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
-			where a.name = c.reference_name
-			and b.name = c.parent
-			and a.name in ({invoice_list_names})
-			group by a.owner, a.posting_date, mode_of_payment
-			union
-			select a.owner, a.posting_date,
-			ifnull(a.voucher_type,'') as mode_of_payment, sum(b.credit)
-			from `tabJournal Entry` a, `tabJournal Entry Account` b
-			where a.name = b.parent
-			and a.docstatus = 1
-			and b.reference_type = "Sales Invoice"
-			and b.reference_name in ({invoice_list_names})
-			group by a.owner, a.posting_date, mode_of_payment
-			""".format(invoice_list_names=invoice_list_names), as_dict=1)
-
-		inv_change_amount = frappe.db.sql("""select a.owner, a.posting_date,
-			ifnull(b.mode_of_payment, '') as mode_of_payment, sum(a.base_change_amount) as change_amount
-			from `tabSales Invoice` a, `tabSales Invoice Payment` b
-			where a.name = b.parent
-			and a.name in ({invoice_list_names})
-			and b.mode_of_payment = 'Cash'
-			and a.base_change_amount > 0
-			group by a.owner, a.posting_date, mode_of_payment""".format(invoice_list_names=invoice_list_names), as_dict=1)
-
-		for d in inv_change_amount:
-			for det in inv_mop_detail:
-				if det["owner"] == d["owner"] and det["posting_date"] == d["posting_date"] and det["mode_of_payment"] == d["mode_of_payment"]:
-					paid_amount = det["paid_amount"] - d["change_amount"]
-					det["paid_amount"] = paid_amount
-
-		payment_details = defaultdict(int)
-		for d in inv_mop_detail:
-			payment_details[d.mode_of_payment] += d.paid_amount
-
-		for m in payment_details:
-			mode_of_payment_details.append({'name': m, 'amount': payment_details[m]})
-
-	return mode_of_payment_details
-
-def get_tax_details(invoice_list):
-	tax_breakup = []
-	tax_details = defaultdict(int)
-	for invoice in invoice_list:
-		doc = frappe.get_doc("Sales Invoice", invoice.name)
-		itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
-
-		if itemised_tax:
-			for a in itemised_tax:
-				for b in itemised_tax[a]:
-					for c in itemised_tax[a][b]:
-						if c == 'tax_rate':
-							tax_details[itemised_tax[a][b][c]] += itemised_tax[a][b]['tax_amount']
-
-	for t in tax_details:
-		tax_breakup.append({'rate': t, 'amount': tax_details[t]})
-
-	return tax_breakup
-
-def get_sales_summary(invoice_list):
-	net_total = sum(item['net_total'] for item in invoice_list)
-	grand_total = sum(item['grand_total'] for item in invoice_list)
-	total_qty = sum(item['pos_total_qty'] for item in invoice_list)
-
-	return {'net_total': net_total, 'grand_total': grand_total, 'total_qty': total_qty}
-
-def get_company_currency(doc):
-	currency = frappe.get_cached_value('Company',  doc.company,  "default_currency")
-	return frappe.get_doc('Currency', currency)
-
-def get_invoices(filters):
-	return frappe.db.sql("""select a.name, a.base_grand_total as grand_total,
-		a.base_net_total as net_total, a.pos_total_qty
-		from `tabSales Invoice` a
-		where a.docstatus = 1 and a.posting_date >= %(from_date)s
-		and a.posting_date <= %(to_date)s and a.company=%(company)s
-		and a.pos_profile = %(pos_profile)s and a.is_pos = %(is_pos)s
-		and a.owner = %(user)s""",
-		filters, as_dict=1)
diff --git a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js b/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js
deleted file mode 100644
index 7633815..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Closing Voucher", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new POS Closing Voucher
-		() => frappe.tests.make('POS Closing Voucher', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py b/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py
deleted file mode 100644
index 8899aaf..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-import frappe
-import unittest
-from frappe.utils import nowdate
-from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
-
-class TestPOSClosingVoucher(unittest.TestCase):
-	def test_pos_closing_voucher(self):
-		old_user = frappe.session.user
-		user = 'test@example.com'
-		test_user = frappe.get_doc('User', user)
-
-		roles = ("Accounts Manager", "Accounts User", "Sales Manager")
-		test_user.add_roles(*roles)
-		frappe.set_user(user)
-
-		pos_profile = make_pos_profile()
-		pos_profile.append('applicable_for_users', {
-			'default': 1,
-			'user': user
-		})
-
-		pos_profile.save()
-
-		si1 = create_sales_invoice(is_pos=1, rate=3500, do_not_submit=1)
-		si1.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500
-		})
-		si1.submit()
-
-		si2 = create_sales_invoice(is_pos=1, rate=3200, do_not_submit=1)
-		si2.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
-		})
-		si2.submit()
-
-		pcv_doc = create_pos_closing_voucher(user=user,
-			pos_profile=pos_profile.name, collected_amount=6700)
-
-		pcv_doc.get_closing_voucher_details()
-
-		self.assertEqual(pcv_doc.total_quantity, 2)
-		self.assertEqual(pcv_doc.net_total, 6700)
-
-		payment = pcv_doc.payment_reconciliation[0]
-		self.assertEqual(payment.mode_of_payment, 'Cash')
-
-		si1.load_from_db()
-		si1.cancel()
-
-		si2.load_from_db()
-		si2.cancel()
-
-		test_user.load_from_db()
-		test_user.remove_roles(*roles)
-
-		frappe.set_user(old_user)
-		frappe.db.sql("delete from `tabPOS Profile`")
-
-def create_pos_closing_voucher(**args):
-	args = frappe._dict(args)
-
-	doc = frappe.get_doc({
-		'doctype': 'POS Closing Voucher',
-		'period_start_date': args.period_start_date or nowdate(),
-		'period_end_date': args.period_end_date or nowdate(),
-		'posting_date': args.posting_date or nowdate(),
-		'company': args.company or "_Test Company",
-		'pos_profile': args.pos_profile,
-		'user': args.user or "Administrator",
-	})
-
-	doc.get_closing_voucher_details()
-	if doc.get('payment_reconciliation'):
-		doc.payment_reconciliation[0].collected_amount = (args.collected_amount or
-			doc.payment_reconciliation[0].expected_amount)
-
-	doc.save()
-	return doc
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/__init__.py b/erpnext/selling/doctype/pos_closing_voucher_details/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_details/__init__.py
+++ /dev/null
diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json b/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json
deleted file mode 100644
index a526884..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json
+++ /dev/null
@@ -1,172 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2018-05-28 19:10:47.580174", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "mode_of_payment", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Mode of Payment", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Mode of Payment", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "0.0", 
-   "fieldname": "collected_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Collected Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "currency", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "expected_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Expected Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "difference", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Difference", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-05-29 17:47:16.311557", 
- "modified_by": "Administrator", 
- "module": "Selling", 
- "name": "POS Closing Voucher Details", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py b/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py
deleted file mode 100644
index 6bc323f..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-from frappe.model.document import Document
-
-class POSClosingVoucherDetails(Document):
-	pass
diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py b/erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py
+++ /dev/null
diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json b/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json
deleted file mode 100644
index 7304550..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json
+++ /dev/null
@@ -1,138 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2018-05-29 14:50:08.687453", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "invoice", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Invoices", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Sales Invoice", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "qty_of_items", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Quantity of Items", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "grand_total", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Grand Total", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-05-29 17:46:46.539993", 
- "modified_by": "Administrator", 
- "module": "Selling", 
- "name": "POS Closing Voucher Invoices", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py b/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py
deleted file mode 100644
index a2d488b..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-from frappe.model.document import Document
-
-class POSClosingVoucherInvoices(Document):
-	pass
diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py b/erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py
+++ /dev/null
diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json b/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json
deleted file mode 100644
index 3089e06..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2018-05-30 09:11:22.535470", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "rate", 
-   "fieldtype": "Percent", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Rate", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-05-30 09:11:22.535470", 
- "modified_by": "Administrator", 
- "module": "Selling", 
- "name": "POS Closing Voucher Taxes", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py b/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
deleted file mode 100644
index 87ce842..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-from frappe.model.document import Document
-
-class POSClosingVoucherTaxes(Document):
-	pass
diff --git a/erpnext/selling/page/point_of_sale/onscan.js b/erpnext/selling/page/point_of_sale/onscan.js
new file mode 100644
index 0000000..428dc75
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/onscan.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t()):e.onScan=t()}(this,function(){var d={attachTo:function(e,t){if(void 0!==e.scannerDetectionData)throw new Error("onScan.js is already initialized for DOM element "+e);var n={onScan:function(e,t){},onScanError:function(e){},onKeyProcess:function(e,t){},onKeyDetect:function(e,t){},onPaste:function(e,t){},keyCodeMapper:function(e){return d.decodeKeyEvent(e)},onScanButtonLongPress:function(){},scanButtonKeyCode:!1,scanButtonLongPressTime:500,timeBeforeScanTest:100,avgTimeByChar:30,minLength:6,suffixKeyCodes:[9,13],prefixKeyCodes:[],ignoreIfFocusOn:!1,stopPropagation:!1,preventDefault:!1,captureEvents:!1,reactToKeydown:!0,reactToPaste:!1,singleScanQty:1};return t=this._mergeOptions(n,t),e.scannerDetectionData={options:t,vars:{firstCharTime:0,lastCharTime:0,accumulatedString:"",testTimer:!1,longPressTimeStart:0,longPressed:!1}},!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste,t.captureEvents),!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp,t.captureEvents),!0!==t.reactToKeydown&&!1===t.scanButtonKeyCode||e.addEventListener("keydown",this._handleKeyDown,t.captureEvents),this},detachFrom:function(e){e.scannerDetectionData.options.reactToPaste&&e.removeEventListener("paste",this._handlePaste),!1!==e.scannerDetectionData.options.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp),e.removeEventListener("keydown",this._handleKeyDown),e.scannerDetectionData=void 0},getOptions:function(e){return e.scannerDetectionData.options},setOptions:function(e,t){switch(e.scannerDetectionData.options.reactToPaste){case!0:!1===t.reactToPaste&&e.removeEventListener("paste",this._handlePaste);break;case!1:!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste)}switch(e.scannerDetectionData.options.scanButtonKeyCode){case!1:!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp);break;default:!1===t.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp)}return e.scannerDetectionData.options=this._mergeOptions(e.scannerDetectionData.options,t),this._reinitialize(e),this},decodeKeyEvent:function(e){var t=this._getNormalizedKeyNum(e);switch(!0){case 48<=t&&t<=90:case 106<=t&&t<=111:if(void 0!==e.key&&""!==e.key)return e.key;var n=String.fromCharCode(t);switch(e.shiftKey){case!1:n=n.toLowerCase();break;case!0:n=n.toUpperCase()}return n;case 96<=t&&t<=105:return t-96}return""},simulate:function(e,t){return this._reinitialize(e),Array.isArray(t)?t.forEach(function(e){var t={};"object"!=typeof e&&"function"!=typeof e||null===e?t.keyCode=parseInt(e):t=e;var n=new KeyboardEvent("keydown",t);document.dispatchEvent(n)}):this._validateScanCode(e,t),this},_reinitialize:function(e){var t=e.scannerDetectionData.vars;t.firstCharTime=0,t.lastCharTime=0,t.accumulatedString=""},_isFocusOnIgnoredElement:function(e){var t=e.scannerDetectionData.options.ignoreIfFocusOn;if(!t)return!1;var n=document.activeElement;if(Array.isArray(t)){for(var a=0;a<t.length;a++)if(!0===n.matches(t[a]))return!0}else if(n.matches(t))return!0;return!1},_validateScanCode:function(e,t){var n,a=e.scannerDetectionData,i=a.options,o=a.options.singleScanQty,r=a.vars.firstCharTime,s=a.vars.lastCharTime,c={};switch(!0){case t.length<i.minLength:c={message:"Receieved code is shorter then minimal length"};break;case s-r>t.length*i.avgTimeByChar:c={message:"Receieved code was not entered in time"};break;default:return i.onScan.call(e,t,o),n=new CustomEvent("scan",{detail:{scanCode:t,qty:o}}),e.dispatchEvent(n),d._reinitialize(e),!0}return c.scanCode=t,c.scanDuration=s-r,c.avgTimeByChar=i.avgTimeByChar,c.minLength=i.minLength,i.onScanError.call(e,c),n=new CustomEvent("scanError",{detail:c}),e.dispatchEvent(n),d._reinitialize(e),!1},_mergeOptions:function(e,t){var n,a={};for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(a[n]=e[n]);for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(a[n]=t[n]);return a},_getNormalizedKeyNum:function(e){return e.which||e.keyCode},_handleKeyDown:function(e){var t=d._getNormalizedKeyNum(e),n=this.scannerDetectionData.options,a=this.scannerDetectionData.vars,i=!1;if(!1!==n.onKeyDetect.call(this,t,e)&&!d._isFocusOnIgnoredElement(this))if(!1===n.scanButtonKeyCode||t!=n.scanButtonKeyCode){switch(!0){case a.firstCharTime&&-1!==n.suffixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!0;break;case!a.firstCharTime&&-1!==n.prefixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!1;break;default:var o=n.keyCodeMapper.call(this,e);if(null===o)return;a.accumulatedString+=o,n.preventDefault&&e.preventDefault(),n.stopPropagation&&e.stopImmediatePropagation(),i=!1}a.firstCharTime||(a.firstCharTime=Date.now()),a.lastCharTime=Date.now(),a.testTimer&&clearTimeout(a.testTimer),i?(d._validateScanCode(this,a.accumulatedString),a.testTimer=!1):a.testTimer=setTimeout(d._validateScanCode,n.timeBeforeScanTest,this,a.accumulatedString),n.onKeyProcess.call(this,o,e)}else a.longPressed||(a.longPressTimer=setTimeout(n.onScanButtonLongPress,n.scanButtonLongPressTime,this),a.longPressed=!0)},_handlePaste:function(e){if(!d._isFocusOnIgnoredElement(this)){e.preventDefault(),oOptions.stopPropagation&&e.stopImmediatePropagation();var t=(event.clipboardData||window.clipboardData).getData("text");this.scannerDetectionData.options.onPaste.call(this,t,event);var n=this.scannerDetectionData.vars;n.firstCharTime=0,n.lastCharTime=0,d._validateScanCode(this,t)}},_handleKeyUp:function(e){d._isFocusOnIgnoredElement(this)||d._getNormalizedKeyNum(e)==this.scannerDetectionData.options.scanButtonKeyCode&&(clearTimeout(this.scannerDetectionData.vars.longPressTimer),this.scannerDetectionData.vars.longPressed=!1)},isScanInProgressFor:function(e){return 0<e.scannerDetectionData.vars.firstCharTime}};return d});
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index 7011cf9..2ce0b27 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -1,5 +1,6 @@
 /* global Clusterize */
-frappe.provide('erpnext.pos');
+frappe.provide('erpnext.PointOfSale');
+{% include "erpnext/selling/page/point_of_sale/pos_controller.js" %}
 frappe.provide('erpnext.queries');
 
 frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
@@ -8,1988 +9,7 @@
 		title: __('Point of Sale'),
 		single_column: true
 	});
-
-	frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
-		if (r && !cint(r.use_pos_in_offline_mode)) {
-			// online
-			wrapper.pos = new erpnext.pos.PointOfSale(wrapper);
-			window.cur_pos = wrapper.pos;
-		} else {
-			// offline
-			frappe.flags.is_offline = true;
-			frappe.set_route('pos');
-		}
-	});
-};
-
-frappe.pages['point-of-sale'].refresh = function(wrapper) {
-	if (wrapper.pos) {
-		wrapper.pos.make_new_invoice();
-	}
-
-	if (frappe.flags.is_offline) {
-		frappe.set_route('pos');
-	}
-}
-
-erpnext.pos.PointOfSale = class PointOfSale {
-	constructor(wrapper) {
-		this.wrapper = $(wrapper).find('.layout-main-section');
-		this.page = wrapper.page;
-
-		const assets = [
-			'assets/erpnext/js/pos/clusterize.js',
-			'assets/erpnext/css/pos.css'
-		];
-
-		frappe.require(assets, () => {
-			this.make();
-		});
-	}
-
-	make() {
-		return frappe.run_serially([
-			() => frappe.dom.freeze(),
-			() => {
-				this.prepare_dom();
-				this.prepare_menu();
-				this.set_online_status();
-			},
-			() => this.make_new_invoice(),
-			() => {
-				if(!this.frm.doc.company) {
-					this.setup_company()
-						.then((company) => {
-							this.frm.doc.company = company;
-							this.get_pos_profile();
-						});
-				}
-			},
-			() => {
-				frappe.dom.unfreeze();
-			},
-			() => this.page.set_title(__('Point of Sale'))
-		]);
-	}
-
-	get_pos_profile() {
-		return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile",
-			{'company': this.frm.doc.company})
-			.then((r) => {
-				if(r) {
-					this.frm.doc.pos_profile = r.name;
-					this.set_pos_profile_data()
-						.then(() => {
-							this.on_change_pos_profile();
-						});
-				} else {
-					this.raise_exception_for_pos_profile();
-				}
-		});
-	}
-
-	set_online_status() {
-		this.connection_status = false;
-		this.page.set_indicator(__("Offline"), "grey");
-		frappe.call({
-			method: "frappe.handler.ping",
-			callback: r => {
-				if (r.message) {
-					this.connection_status = true;
-					this.page.set_indicator(__("Online"), "green");
-				}
-			}
-		});
-	}
-
-	raise_exception_for_pos_profile() {
-		setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000);
-		frappe.throw(__("POS Profile is required to use Point-of-Sale"));
-	}
-
-	prepare_dom() {
-		this.wrapper.append(`
-			<div class="pos">
-				<section class="cart-container">
-
-				</section>
-				<section class="item-container">
-
-				</section>
-			</div>
-		`);
-	}
-
-	make_cart() {
-		this.cart = new POSCart({
-			frm: this.frm,
-			wrapper: this.wrapper.find('.cart-container'),
-			events: {
-				on_customer_change: (customer) => {
-					this.frm.set_value('customer', customer);
-				},
-				on_field_change: (item_code, field, value, batch_no) => {
-					this.update_item_in_cart(item_code, field, value, batch_no);
-				},
-				on_numpad: (value) => {
-					if (value == __('Pay')) {
-						if (!this.payment) {
-							this.make_payment_modal();
-						} else {
-							this.frm.doc.payments.map(p => {
-								this.payment.dialog.set_value(p.mode_of_payment, p.amount);
-							});
-
-							this.payment.set_title();
-						}
-						this.payment.open_modal();
-					}
-				},
-				on_select_change: () => {
-					this.cart.numpad.set_inactive();
-					this.set_form_action();
-				},
-				get_item_details: (item_code) => {
-					return this.items.get(item_code);
-				},
-				get_loyalty_details: () => {
-					var me = this;
-					if (this.frm.doc.customer) {
-						frappe.call({
-							method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
-							args: {
-								"customer": me.frm.doc.customer,
-								"expiry_date": me.frm.doc.posting_date,
-								"company": me.frm.doc.company,
-								"silent": true
-							},
-							callback: function(r) {
-								if (r.message.loyalty_program && r.message.loyalty_points) {
-									me.cart.events.set_loyalty_details(r.message, true);
-								}
-								if (!r.message.loyalty_program) {
-									var loyalty_details = {
-										loyalty_points: 0,
-										loyalty_program: '',
-										expense_account: '',
-										cost_center: ''
-									}
-									me.cart.events.set_loyalty_details(loyalty_details, false);
-								}
-							}
-						});
-					}
-				},
-				set_loyalty_details: (details, view_status) => {
-					if (view_status) {
-						this.cart.available_loyalty_points.$wrapper.removeClass("hide");
-					} else {
-						this.cart.available_loyalty_points.$wrapper.addClass("hide");
-					}
-					this.cart.available_loyalty_points.set_value(details.loyalty_points);
-					this.cart.available_loyalty_points.refresh_input();
-					this.frm.set_value("loyalty_program", details.loyalty_program);
-					this.frm.set_value("loyalty_redemption_account", details.expense_account);
-					this.frm.set_value("loyalty_redemption_cost_center", details.cost_center);
-				}
-			}
-		});
-
-		frappe.ui.form.on('Sales Invoice', 'selling_price_list', (frm) => {
-			if(this.items && frm.doc.pos_profile) {
-				this.items.reset_items();
-			}
-		})
-	}
-
-	toggle_editing(flag) {
-		let disabled;
-		if (flag !== undefined) {
-			disabled = !flag;
-		} else {
-			disabled = this.frm.doc.docstatus == 1 ? true: false;
-		}
-		const pointer_events = disabled ? 'none' : 'inherit';
-
-		this.wrapper.find('input, button, select').prop("disabled", disabled);
-		this.wrapper.find('.number-pad-container').toggleClass("hide", disabled);
-
-		this.wrapper.find('.cart-container').css('pointer-events', pointer_events);
-		this.wrapper.find('.item-container').css('pointer-events', pointer_events);
-
-		this.page.clear_actions();
-	}
-
-	make_items() {
-		this.items = new POSItems({
-			wrapper: this.wrapper.find('.item-container'),
-			frm: this.frm,
-			events: {
-				update_cart: (item, field, value) => {
-					if(!this.frm.doc.customer) {
-						frappe.throw(__('Please select a customer'));
-					}
-					this.update_item_in_cart(item, field, value);
-					this.cart && this.cart.unselect_all();
-				}
-			}
-		});
-	}
-
-	update_item_in_cart(item_code, field='qty', value=1, batch_no) {
-		frappe.dom.freeze();
-		if(this.cart.exists(item_code, batch_no)) {
-			const search_field = batch_no ? 'batch_no' : 'item_code';
-			const search_value = batch_no || item_code;
-			const item = this.frm.doc.items.find(i => i[search_field] === search_value);
-			frappe.flags.hide_serial_batch_dialog = false;
-
-			if (typeof value === 'string' && !in_list(['serial_no', 'batch_no'], field)) {
-				// value can be of type '+1' or '-1'
-				value = item[field] + flt(value);
-			}
-
-			if(field === 'serial_no') {
-				value = item.serial_no + '\n'+ value;
-			}
-
-			// if actual_batch_qty and actual_qty if there is only one batch. In such
-			// a case, no point showing the dialog
-			const show_dialog = item.has_serial_no || item.has_batch_no;
-
-			if (show_dialog && field == 'qty' && ((!item.batch_no && item.has_batch_no) ||
-				(item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) {
-				this.select_batch_and_serial_no(item);
-			} else {
-				this.update_item_in_frm(item, field, value)
-					.then(() => {
-						frappe.dom.unfreeze();
-						frappe.run_serially([
-							() => {
-								let items = this.frm.doc.items.map(item => item.name);
-								if (items && items.length > 0 && items.includes(item.name)) {
-									this.frm.doc.items.forEach(item_row => {
-										// update cart
-										this.on_qty_change(item_row);
-									});
-								} else {
-									this.on_qty_change(item);
-								}
-							},
-							() => this.post_qty_change(item)
-						]);
-					});
-			}
-			return;
-		}
-
-		let args = { item_code: item_code };
-		if (in_list(['serial_no', 'batch_no'], field)) {
-			args[field] = value;
-		}
-
-		// add to cur_frm
-		const item = this.frm.add_child('items', args);
-		frappe.flags.hide_serial_batch_dialog = true;
-
-		frappe.run_serially([
-			() => {
-				return this.frm.script_manager.trigger('item_code', item.doctype, item.name)
-					.then(() => {
-						this.frm.script_manager.trigger('qty', item.doctype, item.name)
-							.then(() => {
-								frappe.run_serially([
-									() => {
-										let items = this.frm.doc.items.map(i => i.name);
-										if (items && items.length > 0 && items.includes(item.name)) {
-											this.frm.doc.items.forEach(item_row => {
-												// update cart
-												this.on_qty_change(item_row);
-											});
-										} else {
-											this.on_qty_change(item);
-										}
-									},
-									() => this.post_qty_change(item)
-								]);
-							});
-					});
-			},
-			() => {
-				const show_dialog = item.has_serial_no || item.has_batch_no;
-
-				// if actual_batch_qty and actual_qty if then there is only one batch. In such
-				// a case, no point showing the dialog
-				if (show_dialog && field == 'qty' && ((!item.batch_no && item.has_batch_no) ||
-					(item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) {
-					// check has serial no/batch no and update cart
-					this.select_batch_and_serial_no(item);
-				}
-			}
-		]);
-	}
-
-	on_qty_change(item) {
-		frappe.run_serially([
-			() => this.update_cart_data(item),
-		]);
-	}
-
-	post_qty_change(item) {
-		this.cart.update_taxes_and_totals();
-		this.cart.update_grand_total();
-		this.cart.update_qty_total();
-		this.cart.scroll_to_item(item.item_code);
-		this.set_form_action();
-	}
-
-	select_batch_and_serial_no(row) {
-		frappe.dom.unfreeze();
-
-		erpnext.show_serial_batch_selector(this.frm, row, () => {
-			this.frm.doc.items.forEach(item => {
-				this.update_item_in_frm(item, 'qty', item.qty)
-					.then(() => {
-						// update cart
-						frappe.run_serially([
-							() => {
-								if (item.qty === 0) {
-									frappe.model.clear_doc(item.doctype, item.name);
-								}
-							},
-							() => this.update_cart_data(item),
-							() => this.post_qty_change(item)
-						]);
-					});
-			})
-		}, () => {
-			this.on_close(row);
-		}, true);
-	}
-
-	on_close(item) {
-		if (!this.cart.exists(item.item_code, item.batch_no) && item.qty) {
-			frappe.model.clear_doc(item.doctype, item.name);
-		}
-	}
-
-	update_cart_data(item) {
-		this.cart.add_item(item);
-		frappe.dom.unfreeze();
-	}
-
-	update_item_in_frm(item, field, value) {
-		if (field == 'qty' && value < 0) {
-			frappe.msgprint(__("Quantity must be positive"));
-			value = item.qty;
-		} else {
-			if (in_list(["qty", "serial_no", "batch"], field)) {
-				item[field] = value;
-				if (field == "serial_no" && value) {
-					let serial_nos = value.split("\n");
-					item["qty"] = serial_nos.filter(d => {
-						return d!=="";
-					}).length;
-				}
-			} else {
-				return frappe.model.set_value(item.doctype, item.name, field, value);
-			}
-		}
-
-		return this.frm.script_manager.trigger('qty', item.doctype, item.name)
-			.then(() => {
-				if (field === 'qty' && item.qty === 0) {
-					frappe.model.clear_doc(item.doctype, item.name);
-				}
-			})
-
-		return Promise.resolve();
-	}
-
-	make_payment_modal() {
-		this.payment = new Payment({
-			frm: this.frm,
-			events: {
-				submit_form: () => {
-					this.submit_sales_invoice();
-				}
-			}
-		});
-	}
-
-	submit_sales_invoice() {
-		this.frm.savesubmit()
-			.then((r) => {
-				if (r && r.doc) {
-					this.frm.doc.docstatus = r.doc.docstatus;
-					frappe.show_alert({
-						indicator: 'green',
-						message: __(`Sales invoice ${r.doc.name} created succesfully`)
-					});
-
-					this.toggle_editing();
-					this.set_form_action();
-					this.set_primary_action_in_modal();
-				}
-			});
-	}
-
-	set_primary_action_in_modal() {
-		if (!this.frm.msgbox) {
-			this.frm.msgbox = frappe.msgprint(
-				`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
-					${__('Print')}</a>
-				<a class="btn btn-default">
-					${__('New')}</a>`
-			);
-
-			$(this.frm.msgbox.body).find('.btn-default').on('click', () => {
-				this.frm.msgbox.hide();
-				this.make_new_invoice();
-			})
-		}
-	}
-
-	change_pos_profile() {
-		return new Promise((resolve) => {
-			const on_submit = ({ company, pos_profile, set_as_default }) => {
-				if (pos_profile) {
-					this.pos_profile = pos_profile;
-				}
-
-				if (set_as_default) {
-					frappe.call({
-						method: "erpnext.accounts.doctype.pos_profile.pos_profile.set_default_profile",
-						args: {
-							'pos_profile': pos_profile,
-							'company': company
-						}
-					}).then(() => {
-						this.on_change_pos_profile();
-					});
-				} else {
-					this.on_change_pos_profile();
-				}
-			}
-
-
-			let me = this;
-
-			var dialog = frappe.prompt([{
-					fieldtype: 'Link',
-					label: __('Company'),
-					options: 'Company',
-					fieldname: 'company',
-					default: me.frm.doc.company,
-					reqd: 1,
-					onchange: function(e) {
-							me.get_default_pos_profile(this.value).then((r) => {
-								dialog.set_value('pos_profile', (r && r.name)? r.name : '');
-							});
-						}
-					},
-					{
-					fieldtype: 'Link',
-					label: __('POS Profile'),
-					options: 'POS Profile',
-					fieldname: 'pos_profile',
-					default: me.frm.doc.pos_profile,
-					reqd: 1,
-					get_query: () => {
-						return {
-							query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
-							filters: {
-								company: dialog.get_value('company')
-							}
-						};
-					}
-				}, {
-					fieldtype: 'Check',
-					label: __('Set as default'),
-					fieldname: 'set_as_default'
-				}],
-				on_submit,
-				__('Select POS Profile')
-			);
-		});
-	}
-
-	on_change_pos_profile() {
-		return frappe.run_serially([
-			() => this.make_sales_invoice_frm(),
-			() => {
-				this.frm.doc.pos_profile = this.pos_profile;
-				this.set_pos_profile_data()
-					.then(() => {
-						this.reset_cart();
-						if (this.items) {
-							this.items.reset_items();
-						}
-					});
-			}
-		]);
-	}
-
-	get_default_pos_profile(company) {
-		return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile",
-			{'company': company})
-	}
-
-	setup_company() {
-		return new Promise(resolve => {
-			if(!this.frm.doc.company) {
-				frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link",
-					label: __("Select Company"), reqd: 1}, (data) => {
-						this.company = data.company;
-						resolve(this.company);
-				}, __("Select Company"));
-			} else {
-				resolve();
-			}
-		})
-	}
-
-	make_new_invoice() {
-		return frappe.run_serially([
-			() => this.make_sales_invoice_frm(),
-			() => this.set_pos_profile_data(),
-			() => {
-				if (this.cart) {
-					this.cart.frm = this.frm;
-					this.cart.reset();
-					this.cart.reset_pos_field_value();
-				} else {
-					this.make_items();
-					this.make_cart();
-				}
-				this.toggle_editing(true);
-			},
-		]);
-	}
-
-	reset_cart() {
-		this.cart.frm = this.frm;
-		this.cart.reset();
-		this.items.reset_search_field();
-	}
-
-	make_sales_invoice_frm() {
-		const doctype = 'Sales Invoice';
-		return new Promise(resolve => {
-			if (this.frm) {
-				this.frm = get_frm(this.frm);
-				if(this.company) {
-					this.frm.doc.company = this.company;
-				}
-
-				resolve();
-			} else {
-				frappe.model.with_doctype(doctype, () => {
-					this.frm = get_frm();
-					resolve();
-				});
-			}
-		});
-
-		function get_frm(_frm) {
-			const page = $('<div>');
-			const frm = _frm || new frappe.ui.form.Form(doctype, page, false);
-			const name = frappe.model.make_new_doc_and_get_name(doctype, true);
-			frm.refresh(name);
-			frm.doc.items = [];
-			frm.doc.is_pos = 1;
-
-			return frm;
-		}
-	}
-
-	set_pos_profile_data() {
-		if (this.company) {
-			this.frm.doc.company = this.company;
-		}
-
-		if (!this.frm.doc.company) {
-			return;
-		}
-
-		return new Promise(resolve => {
-			return this.frm.call({
-				doc: this.frm.doc,
-				method: "set_missing_values",
-			}).then((r) => {
-				if(!r.exc) {
-					if (!this.frm.doc.pos_profile) {
-						frappe.dom.unfreeze();
-						this.raise_exception_for_pos_profile();
-					}
-					this.frm.script_manager.trigger("update_stock");
-					frappe.model.set_default_values(this.frm.doc);
-					this.frm.cscript.calculate_taxes_and_totals();
-
-					if (r.message) {
-						this.frm.meta.default_print_format = r.message.print_format || "";
-						this.frm.allow_edit_rate = r.message.allow_edit_rate;
-						this.frm.allow_edit_discount = r.message.allow_edit_discount;
-						this.frm.doc.campaign = r.message.campaign;
-						this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
-					}
-				}
-
-				resolve();
-			});
-		});
-	}
-
-	prepare_menu() {
-		var me = this;
-		this.page.clear_menu();
-
-		this.page.add_menu_item(__("Form View"), function () {
-			frappe.model.sync(me.frm.doc);
-			frappe.set_route("Form", me.frm.doc.doctype, me.frm.doc.name);
-		});
-
-		this.page.add_menu_item(__("POS Profile"), function () {
-			frappe.set_route('List', 'POS Profile');
-		});
-
-		this.page.add_menu_item(__('POS Settings'), function() {
-			frappe.set_route('Form', 'POS Settings');
-		});
-
-		this.page.add_menu_item(__('Change POS Profile'), function() {
-			me.change_pos_profile();
-		});
-		this.page.add_menu_item(__('Close the POS'), function() {
-			var voucher = frappe.model.get_new_doc('POS Closing Voucher');
-			voucher.pos_profile = me.frm.doc.pos_profile;
-			voucher.user = frappe.session.user;
-			voucher.company = me.frm.doc.company;
-			voucher.period_start_date = me.frm.doc.posting_date;
-			voucher.period_end_date = me.frm.doc.posting_date;
-			voucher.posting_date = me.frm.doc.posting_date;
-			frappe.set_route('Form', 'POS Closing Voucher', voucher.name);
-		});
-	}
-
-	set_form_action() {
-		if(this.frm.doc.docstatus == 1 || (this.frm.allow_print_before_pay == 1 && this.frm.doc.items.length > 0)){
-			this.page.set_secondary_action(__("Print"), async() => {
-				if(this.frm.doc.docstatus != 1 ){
-					await this.frm.save();
-				}
-				this.frm.print_preview.printit(true);
-			});
-		}
-		if(this.frm.doc.items.length == 0){
-			this.page.clear_secondary_action();
-		}
-
-		if (this.frm.doc.docstatus == 1) {
-			this.page.set_primary_action(__("New"), () => {
-				this.make_new_invoice();
-			});
-			this.page.add_menu_item(__("Email"), () => {
-				this.frm.email_doc();
-			});
-		}
-	}
-};
-
-const [Qty,Disc,Rate,Del,Pay] = [__("Qty"), __('Disc'), __('Rate'), __('Del'), __('Pay')];
-
-class POSCart {
-	constructor({frm, wrapper, events}) {
-		this.frm = frm;
-		this.item_data = {};
-		this.wrapper = wrapper;
-		this.events = events;
-		this.make();
-		this.bind_events();
-	}
-
-	make() {
-		this.make_dom();
-		this.make_customer_field();
-		this.make_pos_fields();
-		this.make_loyalty_points();
-		this.make_numpad();
-	}
-
-	make_dom() {
-		this.wrapper.append(`
-			<div class="pos-cart">
-				<div class="customer-field">
-				</div>
-				<div class="pos-field-section" style="margin-bottom:12px; display:none">
-					<a class="h6 uppercase more-fields-section" disabled> ${__("More Information")} </a>
-					<i class="octicon octicon-chevron-down pos-fields-octicon collapse-indicator"
-						style="color:#cacaca; cursor: pointer"></i>
-					<div class="pos-fields" style ="margin-top:12px">
-					</div>
-				</div>
-				<div class="cart-wrapper">
-					<div class="list-item-table">
-						<div class="list-item list-item--head">
-							<div class="list-item__content list-item__content--flex-1.5 text-muted">${__('Item Name')}</div>
-							<div class="list-item__content text-muted text-right">${__('Quantity')}</div>
-							<div class="list-item__content text-muted text-right">${__('Discount')}</div>
-							<div class="list-item__content text-muted text-right">${__('Rate')}</div>
-						</div>
-						<div class="cart-items">
-							<div class="empty-state">
-								<span>${__('No Items added to cart')}</span>
-							</div>
-						</div>
-						<div class="taxes-and-totals">
-							${this.get_taxes_and_totals()}
-						</div>
-						<div class="discount-amount">`+
-						(!this.frm.allow_edit_discount ? `` : `${this.get_discount_amount()}`)+
-						`</div>
-						<div class="grand-total">
-							${this.get_grand_total()}
-						</div>
-						<div class="quantity-total">
-							${this.get_item_qty_total()}
-						</div>
-					</div>
-				</div>
-				<div class="row">
-					<div class="number-pad-container col-sm-6"></div>
-					<div class="col-sm-6 loyalty-program-section">
-						<div class="loyalty-program-field"> </div>
-					</div>
-				</div>
-			</div>
-		`);
-
-
-		this.$cart_items = this.wrapper.find('.cart-items');
-		this.$empty_state = this.wrapper.find('.cart-items .empty-state');
-		this.$taxes_and_totals = this.wrapper.find('.taxes-and-totals');
-		this.$discount_amount = this.wrapper.find('.discount-amount');
-		this.$grand_total = this.wrapper.find('.grand-total');
-		this.$qty_total = this.wrapper.find('.quantity-total');
-		// this.$loyalty_button = this.wrapper.find('.loyalty-button');
-
-		// this.$loyalty_button.on('click', () => {
-		// 	this.loyalty_button.show();
-		// })
-
-		this.toggle_taxes_and_totals(false);
-		this.$grand_total.on('click', () => {
-			this.toggle_taxes_and_totals();
-		});
-	}
-
-	reset() {
-		this.$cart_items.find('.list-item').remove();
-		this.$empty_state.show();
-		this.$taxes_and_totals.html(this.get_taxes_and_totals());
-		this.numpad && this.numpad.reset_value();
-		this.customer_field.set_value("");
-		this.frm.msgbox = "";
-
-		let total_item_qty = 0.0;
-		this.frm.set_value("pos_total_qty",total_item_qty);
-
-		this.$discount_amount.find('input:text').val('');
-		this.wrapper.find('.grand-total-value').text(
-			format_currency(this.frm.doc.grand_total, this.frm.currency));
-		this.wrapper.find('.rounded-total-value').text(
-			format_currency(this.frm.doc.rounded_total, this.frm.currency));
-		this.$qty_total.find(".quantity-total").text(total_item_qty);
-
-		const customer = this.frm.doc.customer;
-		this.customer_field.set_value(customer);
-
-		if (this.numpad) {
-			const disable_btns = this.disable_numpad_control()
-			const enable_btns = [__('Rate'), __('Disc')]
-
-			if (disable_btns) {
-				enable_btns.filter(btn => !disable_btns.includes(btn))
-			}
-
-			this.numpad.enable_buttons(enable_btns);
-		}
-	}
-
-	reset_pos_field_value() {
-		let value = '';
-		if (this.custom_pos_fields) {
-			this.custom_pos_fields.forEach(r => {
-				value = this.frm.doc[r.fieldname] || r.default_value || '';
-
-				if (this.fields) {
-					this.fields[r.fieldname].set_value(value);
-				}
-			})
-		}
-
-		this.wrapper.find('.pos-fields').toggle(false);
-		this.wrapper.find('.pos-fields-octicon').toggle(true);
-	}
-
-	get_grand_total() {
-		let total = this.get_total_template('Grand Total', 'grand-total-value');
-
-		if (!cint(frappe.sys_defaults.disable_rounded_total)) {
-			total += this.get_total_template('Rounded Total', 'rounded-total-value');
-		}
-
-		return total;
-	}
-
-	get_item_qty_total() {
-		let total = this.get_total_template('Total Qty', 'quantity-total');
-		return total;
-	}
-
-	get_total_template(label, class_name) {
-		return `
-			<div class="list-item">
-				<div class="list-item__content text-muted">${__(label)}</div>
-				<div class="list-item__content list-item__content--flex-2 ${class_name}">0.00</div>
-			</div>
-		`;
-	}
-
-	get_discount_amount() {
-		const get_currency_symbol = window.get_currency_symbol;
-
-		return `
-			<div class="list-item">
-				<div class="list-item__content list-item__content--flex-2 text-muted">${__('Discount')}</div>
-				<div class="list-item__content discount-inputs">
-					<input type="text"
-						class="form-control additional_discount_percentage text-right"
-						placeholder="% 0.00"
-					>
-					<input type="text"
-						class="form-control discount_amount text-right"
-						placeholder="${get_currency_symbol(this.frm.doc.currency)} 0.00"
-					>
-				</div>
-			</div>
-		`;
-	}
-
-	get_taxes_and_totals() {
-		return `
-			<div class="list-item">
-				<div class="list-item__content list-item__content--flex-2 text-muted">${__('Net Total')}</div>
-				<div class="list-item__content net-total">0.00</div>
-			</div>
-			<div class="list-item">
-				<div class="list-item__content list-item__content--flex-2 text-muted">${__('Taxes')}</div>
-				<div class="list-item__content taxes">0.00</div>
-			</div>
-		`;
-	}
-
-	toggle_taxes_and_totals(flag) {
-		if (flag !== undefined) {
-			this.tax_area_is_shown = flag;
-		} else {
-			this.tax_area_is_shown = !this.tax_area_is_shown;
-		}
-
-		this.$taxes_and_totals.toggle(this.tax_area_is_shown);
-		this.$discount_amount.toggle(this.tax_area_is_shown);
-	}
-
-	update_taxes_and_totals() {
-		if (!this.frm.doc.taxes) { return; }
-
-		const currency = this.frm.doc.currency;
-		this.frm.refresh_field('taxes');
-
-		// Update totals
-		this.$taxes_and_totals.find('.net-total')
-			.html(format_currency(this.frm.doc.total, currency));
-
-		// Update taxes
-		const taxes_html = this.frm.doc.taxes.map(tax => {
-			return `
-				<div>
-					<span>${tax.description}</span>
-					<span class="text-right bold">
-						${format_currency(tax.tax_amount, currency)}
-					</span>
-				</div>
-			`;
-		}).join("");
-		this.$taxes_and_totals.find('.taxes').html(taxes_html);
-	}
-
-	update_grand_total() {
-		this.$grand_total.find('.grand-total-value').text(
-			format_currency(this.frm.doc.grand_total, this.frm.currency)
-		);
-
-		this.$grand_total.find('.rounded-total-value').text(
-			format_currency(this.frm.doc.rounded_total, this.frm.currency)
-		);
-	}
-
-	update_qty_total() {
-		var total_item_qty = 0;
-		$.each(this.frm.doc["items"] || [], function (i, d) {
-				if (d.qty > 0) {
-					total_item_qty += d.qty;
-				}
-		});
-		this.$qty_total.find('.quantity-total').text(total_item_qty);
-		this.frm.set_value("pos_total_qty",total_item_qty);
-	}
-
-	make_customer_field() {
-		this.customer_field = frappe.ui.form.make_control({
-			df: {
-				fieldtype: 'Link',
-				label: 'Customer',
-				fieldname: 'customer',
-				options: 'Customer',
-				reqd: 1,
-				get_query: function() {
-					return {
-						query: 'erpnext.controllers.queries.customer_query'
-					}
-				},
-				onchange: () => {
-					this.events.on_customer_change(this.customer_field.get_value());
-					this.events.get_loyalty_details();
-				}
-			},
-			parent: this.wrapper.find('.customer-field'),
-			render_input: true
-		});
-
-		this.customer_field.set_value(this.frm.doc.customer);
-	}
-
-	make_pos_fields() {
-		const me = this;
-
-		this.fields = {};
-		this.wrapper.find('.pos-fields-octicon, .more-fields-section').click(() => {
-			this.wrapper.find('.pos-fields').toggle();
-			this.wrapper.find('.pos-fields-octicon').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up');
-		});
-		this.wrapper.find('.pos-fields').toggle(false);
-
-		return new Promise(res => {
-			frappe.call({
-				method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_fields",
-				freeze: true,
-			}).then(r => {
-				if(r.message.length) {
-					this.wrapper.find('.pos-field-section').css('display','block');
-					this.custom_pos_fields = r.message;
-					if (r.message.length < 3) {
-						this.wrapper.find('.pos-fields').toggle(true);
-						this.wrapper.find('.pos-fields-octicon').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up');
-					}
-
-					r.message.forEach(field => {
-						this.fields[field.fieldname] = frappe.ui.form.make_control({
-							df: {
-								fieldtype: field.fieldtype,
-								label: field.label,
-								fieldname: field.fieldname,
-								options: field.options,
-								reqd: field.reqd || 0,
-								read_only: field.read_only || 0,
-								default: field.default_value,
-								onchange: function() {
-									if (this.value) {
-										me.frm.set_value(this.df.fieldname, this.value);
-									}
-								},
-								get_query: () => {
-									return this.get_query_for_pos_fields(field.fieldname)
-								},
-							},
-							parent: this.wrapper.find('.pos-fields'),
-							render_input: true
-						});
-
-						if (this.frm.doc[field.fieldname]) {
-							this.fields[field.fieldname].set_value(this.frm.doc[field.fieldname]);
-						}
-					});
-				}
-			});
-		});
-	}
-
-	get_query_for_pos_fields(field) {
-		if (this.frm.fields_dict && this.frm.fields_dict[field]
-			&& this.frm.fields_dict[field].get_query) {
-			return this.frm.fields_dict[field].get_query(this.frm.doc);
-		}
-	}
-
-	make_loyalty_points() {
-		this.available_loyalty_points = frappe.ui.form.make_control({
-			df: {
-				fieldtype: 'Int',
-				label: 'Available Loyalty Points',
-				read_only: 1,
-				fieldname: 'available_loyalty_points'
-			},
-			parent: this.wrapper.find('.loyalty-program-field')
-		});
-		this.available_loyalty_points.set_value(this.frm.doc.loyalty_points);
-	}
-
-
-	disable_numpad_control() {
-		let disabled_btns = [];
-		if(!this.frm.allow_edit_rate) {
-			disabled_btns.push(__('Rate'));
-		}
-		if(!this.frm.allow_edit_discount) {
-			disabled_btns.push(__('Disc'));
-		}
-		return disabled_btns;
-	}
-
-
-	make_numpad() {
-
-		var pay_class = {}
-		pay_class[__('Pay')]='brand-primary'
-		this.numpad = new NumberPad({
-			button_array: [
-				[1, 2, 3, Qty],
-				[4, 5, 6, Disc],
-				[7, 8, 9, Rate],
-				[Del, 0, '.', Pay]
-			],
-			add_class: pay_class,
-			disable_highlight: [Qty, Disc, Rate, Pay],
-			reset_btns: [Qty, Disc, Rate, Pay],
-			del_btn: Del,
-			disable_btns: this.disable_numpad_control(),
-			wrapper: this.wrapper.find('.number-pad-container'),
-			onclick: (btn_value) => {
-				// on click
-
-				if (!this.selected_item && btn_value !== Pay) {
-					frappe.show_alert({
-						indicator: 'red',
-						message: __('Please select an item in the cart')
-					});
-					return;
-				}
-				if ([Qty, Disc, Rate].includes(btn_value)) {
-					this.set_input_active(btn_value);
-				} else if (btn_value !== Pay) {
-					if (!this.selected_item.active_field) {
-						frappe.show_alert({
-							indicator: 'red',
-							message: __('Please select a field to edit from numpad')
-						});
-						return;
-					}
-
-					if (this.selected_item.active_field == 'discount_percentage' && this.numpad.get_value() > cint(100)) {
-						frappe.show_alert({
-							indicator: 'red',
-							message: __('Discount amount cannot be greater than 100%')
-						});
-						this.numpad.reset_value();
-					} else {
-						const item_code = unescape(this.selected_item.attr('data-item-code'));
-						const batch_no = this.selected_item.attr('data-batch-no');
-						const field = this.selected_item.active_field;
-						const value = this.numpad.get_value();
-
-						this.events.on_field_change(item_code, field, value, batch_no);
-					}
-				}
-
-				this.events.on_numpad(btn_value);
-			}
-		});
-	}
-
-	set_input_active(btn_value) {
-		this.selected_item.removeClass('qty disc rate');
-
-		this.numpad.set_active(btn_value);
-		if (btn_value === Qty) {
-			this.selected_item.addClass('qty');
-			this.selected_item.active_field = 'qty';
-		} else if (btn_value == Disc) {
-			this.selected_item.addClass('disc');
-			this.selected_item.active_field = 'discount_percentage';
-		} else if (btn_value == Rate) {
-			this.selected_item.addClass('rate');
-			this.selected_item.active_field = 'rate';
-		}
-	}
-
-	add_item(item) {
-		this.$empty_state.hide();
-
-		if (this.exists(item.item_code, item.batch_no)) {
-			// update quantity
-			this.update_item(item);
-		} else if (flt(item.qty) > 0.0) {
-			// add to cart
-			const $item = $(this.get_item_html(item));
-			$item.appendTo(this.$cart_items);
-		}
-		this.highlight_item(item.item_code);
-	}
-
-	update_item(item) {
-		const item_selector = item.batch_no ?
-			`[data-batch-no="${item.batch_no}"]` : `[data-item-code="${escape(item.item_code)}"]`;
-
-		const $item = this.$cart_items.find(item_selector);
-
-		if(item.qty > 0) {
-			const is_stock_item = this.get_item_details(item.item_code).is_stock_item;
-			const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red';
-			const remove_class = indicator_class == 'green' ? 'red' : 'green';
-
-			$item.find('.quantity input').val(item.qty);
-			$item.find('.discount').text(item.discount_percentage + '%');
-			$item.find('.rate').text(format_currency(item.rate, this.frm.doc.currency));
-			$item.addClass(indicator_class);
-			$item.removeClass(remove_class);
-		} else {
-			$item.remove();
-		}
-	}
-
-	get_item_html(item) {
-		const is_stock_item = this.get_item_details(item.item_code).is_stock_item;
-		const rate = format_currency(item.rate, this.frm.doc.currency);
-		const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red';
-		const batch_no = item.batch_no || '';
-
-		return `
-			<div class="list-item indicator ${indicator_class}" data-item-code="${escape(item.item_code)}"
-				data-batch-no="${batch_no}" title="Item: ${item.item_name}  Available Qty: ${item.actual_qty} ${item.stock_uom}">
-				<div class="item-name list-item__content list-item__content--flex-1.5 ellipsis">
-					${item.item_name}
-				</div>
-				<div class="quantity list-item__content text-right">
-					${get_quantity_html(item.qty)}
-				</div>
-				<div class="discount list-item__content text-right">
-					${item.discount_percentage}%
-				</div>
-				<div class="rate list-item__content text-right">
-					${rate}
-				</div>
-			</div>
-		`;
-
-		function get_quantity_html(value) {
-			return `
-				<div class="input-group input-group-xs">
-					<span class="input-group-btn">
-						<button class="btn btn-default btn-xs" data-action="increment">+</button>
-					</span>
-
-					<input class="form-control" type="number" value="${value}">
-
-					<span class="input-group-btn">
-						<button class="btn btn-default btn-xs" data-action="decrement">-</button>
-					</span>
-				</div>
-			`;
-		}
-	}
-
-	get_item_details(item_code) {
-		if (!this.item_data[item_code]) {
-			this.item_data[item_code] = this.events.get_item_details(item_code);
-		}
-
-		return this.item_data[item_code];
-	}
-
-	exists(item_code, batch_no) {
-		const is_exists = batch_no ?
-			`[data-batch-no="${batch_no}"]` : `[data-item-code="${escape(item_code)}"]`;
-
-		let $item = this.$cart_items.find(is_exists);
-
-		return $item.length > 0;
-	}
-
-	highlight_item(item_code) {
-		const $item = this.$cart_items.find(`[data-item-code="${escape(item_code)}"]`);
-		$item.addClass('highlight');
-		setTimeout(() => $item.removeClass('highlight'), 1000);
-	}
-
-	scroll_to_item(item_code) {
-		const $item = this.$cart_items.find(`[data-item-code="${escape(item_code)}"]`);
-		if ($item.length === 0) return;
-		const scrollTop = $item.offset().top - this.$cart_items.offset().top + this.$cart_items.scrollTop();
-		this.$cart_items.animate({ scrollTop });
-	}
-
-	bind_events() {
-		const me = this;
-		const events = this.events;
-
-		// quantity change
-		this.$cart_items.on('click',
-			'[data-action="increment"], [data-action="decrement"]', function() {
-				const $btn = $(this);
-				const $item = $btn.closest('.list-item[data-item-code]');
-				const item_code = unescape($item.attr('data-item-code'));
-				const action = $btn.attr('data-action');
-
-				if(action === 'increment') {
-					events.on_field_change(item_code, 'qty', '+1');
-				} else if(action === 'decrement') {
-					events.on_field_change(item_code, 'qty', '-1');
-				}
-			});
-
-		this.$cart_items.on('change', '.quantity input', function() {
-			const $input = $(this);
-			const $item = $input.closest('.list-item[data-item-code]');
-			const item_code = unescape($item.attr('data-item-code'));
-			events.on_field_change(item_code, 'qty', flt($input.val()));
-		});
-
-		// current item
-		this.$cart_items.on('click', '.list-item', function() {
-			me.set_selected_item($(this));
-		});
-
-		this.wrapper.find('.additional_discount_percentage').on('change', (e) => {
-			const discount_percentage = flt(e.target.value,
-				precision("additional_discount_percentage"));
-
-			frappe.model.set_value(this.frm.doctype, this.frm.docname,
-				'additional_discount_percentage', discount_percentage)
-				.then(() => {
-					let discount_wrapper = this.wrapper.find('.discount_amount');
-					discount_wrapper.val(flt(this.frm.doc.discount_amount,
-						precision('discount_amount')));
-					discount_wrapper.trigger('change');
-				});
-		});
-
-		this.wrapper.find('.discount_amount').on('change', (e) => {
-			const discount_amount = flt(e.target.value, precision('discount_amount'));
-			frappe.model.set_value(this.frm.doctype, this.frm.docname,
-				'discount_amount', discount_amount);
-			this.frm.trigger('discount_amount')
-				.then(() => {
-					this.update_discount_fields();
-					this.update_taxes_and_totals();
-					this.update_grand_total();
-				});
-		});
-	}
-
-	update_discount_fields() {
-		let discount_wrapper = this.wrapper.find('.additional_discount_percentage');
-		let discount_amt_wrapper = this.wrapper.find('.discount_amount');
-		discount_wrapper.val(flt(this.frm.doc.additional_discount_percentage,
-			precision('additional_discount_percentage')));
-		discount_amt_wrapper.val(flt(this.frm.doc.discount_amount,
-			precision('discount_amount')));
-	}
-
-	set_selected_item($item) {
-		this.selected_item = $item;
-		this.$cart_items.find('.list-item').removeClass('current-item qty disc rate');
-		this.selected_item.addClass('current-item');
-		this.events.on_select_change();
-	}
-
-	unselect_all() {
-		this.$cart_items.find('.list-item').removeClass('current-item qty disc rate');
-		this.selected_item = null;
-		this.events.on_select_change();
-	}
-}
-
-class POSItems {
-	constructor({wrapper, frm, events}) {
-		this.wrapper = wrapper;
-		this.frm = frm;
-		this.items = {};
-		this.events = events;
-		this.currency = this.frm.doc.currency;
-
-		frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name", (r) => {
-			this.parent_item_group = r.name;
-			this.make_dom();
-			this.make_fields();
-
-			this.init_clusterize();
-			this.bind_events();
-			this.load_items_data();
-		})
-	}
-
-	load_items_data() {
-		// bootstrap with 20 items
-		this.get_items()
-			.then(({ items }) => {
-				this.all_items = items;
-				this.items = items;
-				this.render_items(items);
-			});
-	}
-
-	reset_items() {
-		this.wrapper.find('.pos-items').empty();
-		this.init_clusterize();
-		this.load_items_data();
-	}
-
-	make_dom() {
-		this.wrapper.html(`
-			<div class="fields">
-				<div class="search-field">
-				</div>
-				<div class="item-group-field">
-				</div>
-			</div>
-			<div class="items-wrapper">
-			</div>
-		`);
-
-		this.items_wrapper = this.wrapper.find('.items-wrapper');
-		this.items_wrapper.append(`
-			<div class="list-item-table pos-items-wrapper">
-				<div class="pos-items image-view-container">
-				</div>
-			</div>
-		`);
-	}
-
-	make_fields() {
-		// Search field
-		const me = this;
-		this.search_field = frappe.ui.form.make_control({
-			df: {
-				fieldtype: 'Data',
-				label: __('Search Item (Ctrl + i)'),
-				placeholder: __('Search by item code, serial number, batch no or barcode')
-			},
-			parent: this.wrapper.find('.search-field'),
-			render_input: true,
-		});
-
-		frappe.ui.keys.on('ctrl+i', () => {
-			this.search_field.set_focus();
-		});
-
-		this.search_field.$input.on('input', (e) => {
-			clearTimeout(this.last_search);
-			this.last_search = setTimeout(() => {
-				const search_term = e.target.value;
-				const item_group = this.item_group_field ?
-					this.item_group_field.get_value() : '';
-
-				this.filter_items({ search_term:search_term,  item_group: item_group});
-			}, 300);
-		});
-
-		this.item_group_field = frappe.ui.form.make_control({
-			df: {
-				fieldtype: 'Link',
-				label: 'Item Group',
-				options: 'Item Group',
-				default: me.parent_item_group,
-				onchange: () => {
-					const item_group = this.item_group_field.get_value();
-					if (item_group) {
-						this.filter_items({ item_group: item_group });
-					}
-				},
-				get_query: () => {
-					return {
-						query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query',
-						filters: {
-							pos_profile: this.frm.doc.pos_profile
-						}
-					};
-				}
-			},
-			parent: this.wrapper.find('.item-group-field'),
-			render_input: true
-		});
-	}
-
-	init_clusterize() {
-		this.clusterize = new Clusterize({
-			scrollElem: this.wrapper.find('.pos-items-wrapper')[0],
-			contentElem: this.wrapper.find('.pos-items')[0],
-			rows_in_block: 6
-		});
-	}
-
-	render_items(items) {
-		let _items = items || this.items;
-
-		const all_items = Object.values(_items).map(item => this.get_item_html(item));
-		let row_items = [];
-
-		const row_container = '<div class="image-view-row">';
-		let curr_row = row_container;
-
-		for (let i=0; i < all_items.length; i++) {
-			// wrap 4 items in a div to emulate
-			// a row for clusterize
-			if(i % 4 === 0 && i !== 0) {
-				curr_row += '</div>';
-				row_items.push(curr_row);
-				curr_row = row_container;
-			}
-			curr_row += all_items[i];
-
-			if(i == all_items.length - 1) {
-				row_items.push(curr_row);
-			}
-		}
-
-		this.clusterize.update(row_items);
-	}
-
-	filter_items({ search_term='', item_group=this.parent_item_group }={}) {
-		if (search_term) {
-			search_term = search_term.toLowerCase();
-
-			// memoize
-			this.search_index = this.search_index || {};
-			if (this.search_index[search_term]) {
-				const items = this.search_index[search_term];
-				this.items = items;
-				this.render_items(items);
-				this.set_item_in_the_cart(items);
-				return;
-			}
-		} else if (item_group == this.parent_item_group) {
-			this.items = this.all_items;
-			return this.render_items(this.all_items);
-		}
-
-		this.get_items({search_value: search_term, item_group })
-			.then(({ items, serial_no, batch_no, barcode }) => {
-				if (search_term && !barcode) {
-					this.search_index[search_term] = items;
-				}
-
-				this.items = items;
-				this.render_items(items);
-				this.set_item_in_the_cart(items, serial_no, batch_no, barcode);
-			});
-	}
-
-	set_item_in_the_cart(items, serial_no, batch_no, barcode) {
-		if (serial_no) {
-			this.events.update_cart(items[0].item_code,
-				'serial_no', serial_no);
-			this.reset_search_field();
-			return;
-		}
-
-		if (batch_no) {
-			this.events.update_cart(items[0].item_code,
-				'batch_no', batch_no);
-			this.reset_search_field();
-			return;
-		}
-
-		if (items.length === 1 && (serial_no || batch_no || barcode)) {
-			this.events.update_cart(items[0].item_code,
-				'qty', '+1');
-			this.reset_search_field();
-		}
-	}
-
-	reset_search_field() {
-		this.search_field.set_value('');
-		this.search_field.$input.trigger("input");
-	}
-
-	bind_events() {
-		var me = this;
-		this.wrapper.on('click', '.pos-item-wrapper', function() {
-			const $item = $(this);
-			const item_code = unescape($item.attr('data-item-code'));
-			me.events.update_cart(item_code, 'qty', '+1');
-		});
-	}
-
-	get(item_code) {
-		let item = {};
-		this.items.map(data => {
-			if (data.item_code === item_code) {
-				item = data;
-			}
-		})
-
-		return item
-	}
-
-	get_all() {
-		return this.items;
-	}
-
-	get_item_html(item) {
-		const price_list_rate = format_currency(item.price_list_rate, this.currency);
-		const { item_code, item_name, item_image} = item;
-		const item_title = item_name || item_code;
-
-		const template = `
-			<div class="pos-item-wrapper image-view-item" data-item-code="${escape(item_code)}">
-				<div class="image-view-header">
-					<div>
-						<a class="grey list-id" data-name="${item_code}" title="${item_title}">
-							${item_title}
-						</a>
-					</div>
-				</div>
-				<div class="image-view-body">
-					<a	data-item-code="${item_code}"
-						title="${item_title}"
-					>
-						<div class="image-field"
-							style="${!item_image ? 'background-color: #fafbfc;' : ''} border: 0px;"
-						>
-							${!item_image ? `<span class="placeholder-text">
-									${frappe.get_abbr(item_title)}
-								</span>` : '' }
-							${item_image ? `<img src="${item_image}" alt="${item_title}">` : '' }
-						</div>
-						<span class="price-info">
-							${price_list_rate}
-						</span>
-					</a>
-				</div>
-			</div>
-		`;
-
-		return template;
-	}
-
-	get_items({start = 0, page_length = 40, search_value='', item_group=this.parent_item_group}={}) {
-		const price_list = this.frm.doc.selling_price_list;
-		return new Promise(res => {
-			frappe.call({
-				method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
-				freeze: true,
-				args: {
-					start,
-					page_length,
-					price_list,
-					item_group,
-					search_value,
-					pos_profile: this.frm.doc.pos_profile
-				}
-			}).then(r => {
-				// const { items, serial_no, batch_no } = r.message;
-
-				// this.serial_no = serial_no || "";
-				res(r.message);
-			});
-		});
-	}
-}
-
-class NumberPad {
-	constructor({
-		wrapper, onclick, button_array,
-		add_class={}, disable_highlight=[],
-		reset_btns=[], del_btn='', disable_btns
-	}) {
-		this.wrapper = wrapper;
-		this.onclick = onclick;
-		this.button_array = button_array;
-		this.add_class = add_class;
-		this.disable_highlight = disable_highlight;
-		this.reset_btns = reset_btns;
-		this.del_btn = del_btn;
-		this.disable_btns = disable_btns || [];
-		this.make_dom();
-		this.bind_events();
-		this.value = '';
-	}
-
-	make_dom() {
-		if (!this.button_array) {
-			this.button_array = [
-				[1, 2, 3],
-				[4, 5, 6],
-				[7, 8, 9],
-				['', 0, '']
-			];
-		}
-
-		this.wrapper.html(`
-			<div class="number-pad">
-				${this.button_array.map(get_row).join("")}
-			</div>
-		`);
-
-		function get_row(row) {
-			return '<div class="num-row">' + row.map(get_col).join("") + '</div>';
-		}
-
-		function get_col(col) {
-			return `<div class="num-col" data-value="${col}"><div>${col}</div></div>`;
-		}
-
-		this.set_class();
-
-		if(this.disable_btns) {
-			this.disable_btns.forEach((btn) => {
-				const $btn = this.get_btn(btn);
-				$btn.prop("disabled", true)
-				$btn.hover(() => {
-					$btn.css('cursor','not-allowed');
-				})
-			})
-		}
-	}
-
-	enable_buttons(btns) {
-		btns.forEach((btn) => {
-			const $btn = this.get_btn(btn);
-			$btn.prop("disabled", false)
-			$btn.hover(() => {
-				$btn.css('cursor','pointer');
-			})
-		})
-	}
-
-	set_class() {
-		for (const btn in this.add_class) {
-			const class_name = this.add_class[btn];
-			this.get_btn(btn).addClass(class_name);
-		}
-	}
-
-	bind_events() {
-		// bind click event
-		const me = this;
-		this.wrapper.on('click', '.num-col', function() {
-			const $btn = $(this);
-			const btn_value = $btn.attr('data-value');
-			if (!me.disable_highlight.includes(btn_value)) {
-				me.highlight_button($btn);
-			}
-			if (me.reset_btns.includes(btn_value)) {
-				me.reset_value();
-			} else {
-				if (btn_value === me.del_btn) {
-					me.value = me.value.substr(0, me.value.length - 1);
-				} else {
-					me.value += btn_value;
-				}
-			}
-			me.onclick(btn_value);
-		});
-	}
-
-	reset_value() {
-		this.value = '';
-	}
-
-	get_value() {
-		return flt(this.value);
-	}
-
-	get_btn(btn_value) {
-		return this.wrapper.find(`.num-col[data-value="${btn_value}"]`);
-	}
-
-	highlight_button($btn) {
-		$btn.addClass('highlight');
-		setTimeout(() => $btn.removeClass('highlight'), 1000);
-	}
-
-	set_active(btn_value) {
-		const $btn = this.get_btn(btn_value);
-		this.wrapper.find('.num-col').removeClass('active');
-		$btn.addClass('active');
-	}
-
-	set_inactive() {
-		this.wrapper.find('.num-col').removeClass('active');
-	}
-}
-
-class Payment {
-	constructor({frm, events}) {
-		this.frm = frm;
-		this.events = events;
-		this.make();
-		this.bind_events();
-		this.set_primary_action();
-	}
-
-	open_modal() {
-		this.dialog.show();
-	}
-
-	make() {
-		this.set_flag();
-		this.dialog = new frappe.ui.Dialog({
-			fields: this.get_fields(),
-			width: 800,
-			invoice_frm: this.frm
-		});
-
-		this.set_title();
-
-		this.$body = this.dialog.body;
-
-		this.numpad = new NumberPad({
-			wrapper: $(this.$body).find('[data-fieldname="numpad"]'),
-			button_array: [
-				[1, 2, 3],
-				[4, 5, 6],
-				[7, 8, 9],
-				[__('Del'), 0, '.'],
-			],
-			onclick: () => {
-				if(this.fieldname) {
-					this.dialog.set_value(this.fieldname, this.numpad.get_value());
-				}
-			}
-		});
-	}
-
-	set_title() {
-		let title = __('Total Amount {0}',
-			[format_currency(this.frm.doc.rounded_total || this.frm.doc.grand_total,
-			this.frm.doc.currency)]);
-
-		this.dialog.set_title(title);
-	}
-
-	bind_events() {
-		var me = this;
-		$(this.dialog.body).find('.input-with-feedback').focusin(function() {
-			me.numpad.reset_value();
-			me.fieldname = $(this).prop('dataset').fieldname;
-			if (me.frm.doc.outstanding_amount > 0 &&
-				!in_list(['write_off_amount', 'change_amount'], me.fieldname)) {
-				me.frm.doc.payments.forEach((data) => {
-					if (data.mode_of_payment == me.fieldname && !data.amount) {
-						me.dialog.set_value(me.fieldname,
-							me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate);
-						return;
-					}
-				})
-			}
-		});
-	}
-
-	set_primary_action() {
-		var me = this;
-
-		this.dialog.set_primary_action(__("Submit"), function() {
-			me.dialog.hide();
-			me.events.submit_form();
-		});
-	}
-
-	get_fields() {
-		const me = this;
-
-		let fields = this.frm.doc.payments.map(p => {
-			return {
-				fieldtype: 'Currency',
-				label: __(p.mode_of_payment),
-				options: me.frm.doc.currency,
-				fieldname: p.mode_of_payment,
-				default: p.amount,
-				onchange: () => {
-					const value = this.dialog.get_value(this.fieldname) || 0;
-					me.update_payment_value(this.fieldname, value);
-				}
-			};
-		});
-
-		fields = fields.concat([
-			{
-				fieldtype: 'Column Break',
-			},
-			{
-				fieldtype: 'HTML',
-				fieldname: 'numpad'
-			},
-			{
-				fieldtype: 'Section Break',
-				depends_on: 'eval: this.invoice_frm.doc.loyalty_program'
-			},
-			{
-				fieldtype: 'Check',
-				label: 'Redeem Loyalty Points',
-				fieldname: 'redeem_loyalty_points',
-				onchange: () => {
-					me.update_cur_frm_value("redeem_loyalty_points", () => {
-						frappe.flags.redeem_loyalty_points = false;
-						me.update_loyalty_points();
-					});
-				}
-			},
-			{
-				fieldtype: 'Column Break',
-			},
-			{
-				fieldtype: 'Int',
-				fieldname: "loyalty_points",
-				label: __("Loyalty Points"),
-				depends_on: "redeem_loyalty_points",
-				onchange: () => {
-					me.update_cur_frm_value("loyalty_points", () => {
-						frappe.flags.loyalty_points = false;
-						me.update_loyalty_points();
-					});
-				}
-			},
-			{
-				fieldtype: 'Currency',
-				label: __("Loyalty Amount"),
-				fieldname: "loyalty_amount",
-				options: me.frm.doc.currency,
-				read_only: 1,
-				depends_on: "redeem_loyalty_points"
-			},
-			{
-				fieldtype: 'Section Break',
-			},
-			{
-				fieldtype: 'Currency',
-				label: __("Write off Amount"),
-				options: me.frm.doc.currency,
-				fieldname: "write_off_amount",
-				default: me.frm.doc.write_off_amount,
-				onchange: () => {
-					me.update_cur_frm_value('write_off_amount', () => {
-						frappe.flags.change_amount = false;
-						me.update_change_amount();
-					});
-				}
-			},
-			{
-				fieldtype: 'Column Break',
-			},
-			{
-				fieldtype: 'Currency',
-				label: __("Change Amount"),
-				options: me.frm.doc.currency,
-				fieldname: "change_amount",
-				default: me.frm.doc.change_amount,
-				onchange: () => {
-					me.update_cur_frm_value('change_amount', () => {
-						frappe.flags.write_off_amount = false;
-						me.update_write_off_amount();
-					});
-				}
-			},
-			{
-				fieldtype: 'Section Break',
-			},
-			{
-				fieldtype: 'Currency',
-				label: __("Paid Amount"),
-				options: me.frm.doc.currency,
-				fieldname: "paid_amount",
-				default: me.frm.doc.paid_amount,
-				read_only: 1
-			},
-			{
-				fieldtype: 'Column Break',
-			},
-			{
-				fieldtype: 'Currency',
-				label: __("Outstanding Amount"),
-				options: me.frm.doc.currency,
-				fieldname: "outstanding_amount",
-				default: me.frm.doc.outstanding_amount,
-				read_only: 1
-			},
-		]);
-
-		return fields;
-	}
-
-	set_flag() {
-		frappe.flags.write_off_amount = true;
-		frappe.flags.change_amount = true;
-		frappe.flags.loyalty_points = true;
-		frappe.flags.redeem_loyalty_points = true;
-		frappe.flags.payment_method = true;
-	}
-
-	update_cur_frm_value(fieldname, callback) {
-		if (frappe.flags[fieldname]) {
-			const value = this.dialog.get_value(fieldname);
-			this.frm.set_value(fieldname, value)
-				.then(() => {
-					callback();
-				});
-		}
-
-		frappe.flags[fieldname] = true;
-	}
-
-	update_payment_value(fieldname, value) {
-		var me = this;
-			$.each(this.frm.doc.payments, function(i, data) {
-				if (__(data.mode_of_payment) == __(fieldname)) {
-					frappe.model.set_value('Sales Invoice Payment', data.name, 'amount', value)
-						.then(() => {
-							me.update_change_amount();
-							me.update_write_off_amount();
-						});
-				}
-			});
-	}
-
-	update_change_amount() {
-		this.dialog.set_value("change_amount", this.frm.doc.change_amount);
-		this.show_paid_amount();
-	}
-
-	update_write_off_amount() {
-		this.dialog.set_value("write_off_amount", this.frm.doc.write_off_amount);
-	}
-
-	show_paid_amount() {
-		this.dialog.set_value("paid_amount", this.frm.doc.paid_amount);
-		this.dialog.set_value("outstanding_amount", this.frm.doc.outstanding_amount);
-	}
-
-	update_payment_amount() {
-		var me = this;
-		$.each(this.frm.doc.payments, function(i, data) {
-			console.log("setting the ", data.mode_of_payment, " for the value", data.amount);
-			me.dialog.set_value(data.mode_of_payment, data.amount);
-		});
-	}
-
-	update_loyalty_points() {
-		if (this.dialog.get_value("redeem_loyalty_points")) {
-			this.dialog.set_value("loyalty_points", this.frm.doc.loyalty_points);
-			this.dialog.set_value("loyalty_amount", this.frm.doc.loyalty_amount);
-			this.update_payment_amount();
-			this.show_paid_amount();
-		}
-	}
-
-}
+	// online
+	wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
+	window.cur_pos = wrapper.pos;
+};
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.json b/erpnext/selling/page/point_of_sale/point_of_sale.json
index 6d2f5f2..99b86e4 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.json
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.json
@@ -1,33 +1,33 @@
 {
- "content": null, 
- "creation": "2017-08-07 17:08:56.737947", 
- "docstatus": 0, 
- "doctype": "Page", 
- "idx": 0, 
- "modified": "2017-09-11 13:49:05.415211", 
- "modified_by": "Administrator", 
- "module": "Selling", 
- "name": "point-of-sale", 
- "owner": "Administrator", 
- "page_name": "Point of Sale", 
- "restrict_to_domain": "Retail", 
+ "content": null,
+ "creation": "2020-01-28 22:05:44.819140",
+ "docstatus": 0,
+ "doctype": "Page",
+ "idx": 0,
+ "modified": "2020-06-01 15:41:06.348380",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "point-of-sale",
+ "owner": "Administrator",
+ "page_name": "Point of Sale",
+ "restrict_to_domain": "Retail",
  "roles": [
   {
    "role": "Accounts User"
-  }, 
+  },
   {
    "role": "Accounts Manager"
-  }, 
+  },
   {
    "role": "Sales User"
-  }, 
+  },
   {
    "role": "Sales Manager"
   }
- ], 
- "script": null, 
- "standard": "Yes", 
- "style": null, 
- "system_page": 0, 
- "title": "Point of Sale"
+ ],
+ "script": null,
+ "standard": "Yes",
+ "style": null,
+ "system_page": 0,
+ "title": "Point Of Sale"
 }
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 1ae1fde..f7b7ed8 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -6,6 +6,7 @@
 from frappe.utils.nestedset import get_root_of
 from frappe.utils import cint
 from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups
+from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability
 
 from six import string_types
 
@@ -43,6 +44,7 @@
 		SELECT
 			name AS item_code,
 			item_name,
+			description,
 			stock_uom,
 			image AS item_image,
 			idx AS idx,
@@ -53,10 +55,11 @@
 			disabled = 0
 				AND has_variants = 0
 				AND is_sales_item = 1
+				AND is_fixed_asset = 0
 				AND item_group in (SELECT name FROM `tabItem Group` WHERE lft >= {lft} AND rgt <= {rgt})
 				AND {condition}
 		ORDER BY
-			idx desc
+			name asc
 		LIMIT
 			{start}, {page_length}"""
 		.format(
@@ -73,32 +76,14 @@
 			fields = ["item_code", "price_list_rate", "currency"],
 			filters = {'price_list': price_list, 'item_code': ['in', items]})
 
-		item_prices, bin_data = {}, {}
+		item_prices = {}
 		for d in item_prices_data:
 			item_prices[d.item_code] = d
 
-		# prepare filter for bin query
-		bin_filters = {'item_code': ['in', items]}
-		if warehouse:
-			bin_filters['warehouse'] = warehouse
-		if display_items_in_stock:
-			bin_filters['actual_qty'] = [">", 0]
-
-		# query item bin
-		bin_data = frappe.get_all(
-			'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'],
-			filters=bin_filters, group_by='item_code'
-		)
-
-		# convert list of dict into dict as {item_code: actual_qty}
-		bin_dict = {}
-		for b in bin_data:
-			bin_dict[b.get('item_code')] = b.get('actual_qty')
-
 		for item in items_data:
 			item_code = item.item_code
 			item_price = item_prices.get(item_code) or {}
-			item_stock_qty = bin_dict.get(item_code)
+			item_stock_qty = get_stock_availability(item_code, warehouse)
 
 			if display_items_in_stock and not item_stock_qty:
 				pass
@@ -116,6 +101,13 @@
 		'items': result
 	}
 
+	if len(res['items']) == 1:
+		res['items'][0].setdefault('serial_no', serial_no)
+		res['items'][0].setdefault('batch_no', batch_no)
+		res['items'][0].setdefault('barcode', barcode)
+
+		return res
+
 	if serial_no:
 		res.update({
 			'serial_no': serial_no
@@ -186,6 +178,73 @@
 			{'txt': '%%%s%%' % txt})
 
 @frappe.whitelist()
-def get_pos_fields():
-	return frappe.get_all("POS Field", fields=["label", "fieldname",
-		"fieldtype", "default_value", "reqd", "read_only", "options"])
+def check_opening_entry(user):
+	open_vouchers = frappe.db.get_all("POS Opening Entry", 
+		filters = { 
+			"user": user, 
+			"pos_closing_entry": ["in", ["", None]],
+			"docstatus": 1
+		}, 
+		fields = ["name", "company", "pos_profile", "period_start_date"],
+		order_by = "period_start_date desc"
+	)
+
+	return open_vouchers
+
+@frappe.whitelist()
+def create_opening_voucher(pos_profile, company, balance_details):
+	import json
+	balance_details = json.loads(balance_details)
+
+	new_pos_opening = frappe.get_doc({
+		'doctype': 'POS Opening Entry',
+		"period_start_date": frappe.utils.get_datetime(),
+		"posting_date": frappe.utils.getdate(),
+		"user": frappe.session.user,
+		"pos_profile": pos_profile,
+		"company": company,
+	})
+	new_pos_opening.set("balance_details", balance_details)
+	new_pos_opening.submit()
+
+	return new_pos_opening.as_dict()
+
+@frappe.whitelist()
+def get_past_order_list(search_term, status, limit=20):
+	fields = ['name', 'grand_total', 'currency', 'customer', 'posting_time', 'posting_date']
+	invoice_list = []
+
+	if search_term and status:
+		invoices_by_customer = frappe.db.get_all('POS Invoice', filters={
+			'customer': ['like', '%{}%'.format(search_term)],
+			'status': status
+		}, fields=fields)
+		invoices_by_name = frappe.db.get_all('POS Invoice', filters={
+			'name': ['like', '%{}%'.format(search_term)],
+			'status': status
+		}, fields=fields)
+
+		invoice_list = invoices_by_customer + invoices_by_name
+	elif status:
+		invoice_list = frappe.db.get_all('POS Invoice', filters={
+			'status': status
+		}, fields=fields)
+	
+	return invoice_list
+
+@frappe.whitelist()
+def set_customer_info(fieldname, customer, value=""):
+	if fieldname == 'loyalty_program':
+		frappe.db.set_value('Customer', customer, 'loyalty_program', value)
+
+	contact = frappe.get_cached_value('Customer', customer, 'customer_primary_contact')
+
+	if contact:
+		contact_doc = frappe.get_doc('Contact', contact)
+		if fieldname == 'email_id':
+			contact_doc.set('email_ids', [{ 'email_id': value, 'is_primary': 1}])
+			frappe.db.set_value('Customer', customer, 'email_id', value)
+		elif fieldname == 'mobile_no': 
+			contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
+			frappe.db.set_value('Customer', customer, 'mobile_no', value)
+		contact_doc.save()
\ 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
new file mode 100644
index 0000000..483ef78
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -0,0 +1,714 @@
+{% include "erpnext/selling/page/point_of_sale/onscan.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_item_selector.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_item_cart.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_item_details.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_payment.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_past_order_list.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_past_order_summary.js" %}
+
+erpnext.PointOfSale.Controller = class {
+    constructor(wrapper) {
+		this.wrapper = $(wrapper).find('.layout-main-section');
+		this.page = wrapper.page;
+
+		this.load_assets();
+	}
+
+	load_assets() {
+		// after loading assets first check if opening entry has been made
+		frappe.require(['assets/erpnext/css/pos.css'], this.check_opening_entry.bind(this));
+	}
+
+	check_opening_entry() {
+		return frappe.call("erpnext.selling.page.point_of_sale.point_of_sale.check_opening_entry", { "user": frappe.session.user })
+			.then((r) => {
+				if (r.message.length) {
+					// assuming only one opening voucher is available for the current user
+					this.prepare_app_defaults(r.message[0]);
+				} else {
+					this.create_opening_voucher();
+				}
+			});
+	}
+
+	create_opening_voucher() {
+		const table_fields = [
+			{ fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 },
+			{ fieldname: "opening_amount", fieldtype: "Currency", in_list_view: 1, label: "Opening Amount", options: "company:company_currency", reqd: 1 }
+		];
+
+		const dialog = new frappe.ui.Dialog({
+			title: __('Create POS Opening Entry'),
+			fields: [
+				{
+					fieldtype: 'Link', label: __('Company'), default: frappe.defaults.get_default('company'),
+					options: 'Company', fieldname: 'company', reqd: 1
+				},
+				{
+					fieldtype: 'Link', label: __('POS Profile'),
+					options: 'POS Profile', fieldname: 'pos_profile', reqd: 1,
+					onchange: () => {
+						const pos_profile = dialog.fields_dict.pos_profile.get_value();
+						const company = dialog.fields_dict.company.get_value();
+						const user = frappe.session.user
+
+						if (!pos_profile || !company || !user) return;
+
+						// auto fetch last closing entry's balance details
+						frappe.db.get_list("POS Closing Entry", {
+							filters: { company, pos_profile, user },
+							limit: 1,
+							order_by: 'period_end_date desc'
+						}).then((res) => {
+							if (!res.length) return;
+							const pos_closing_entry = res[0];
+							frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => {
+								dialog.fields_dict.balance_details.df.data = [];
+								payment_reconciliation.forEach(pay => {
+									const { mode_of_payment, closing_amount } = pay;
+									dialog.fields_dict.balance_details.df.data.push({
+										mode_of_payment: mode_of_payment
+									});
+								});
+								dialog.fields_dict.balance_details.grid.refresh();
+							});
+						});
+					}
+				},
+				{
+					fieldname: "balance_details",
+					fieldtype: "Table",
+					label: "Opening Balance Details",
+					cannot_add_rows: false,
+					in_place_edit: true,
+					reqd: 1,
+					data: [],
+					fields: table_fields
+				}
+			],
+			primary_action: ({ company, pos_profile, balance_details }) => {
+				if (!balance_details.length) {
+					frappe.show_alert({
+						message: __("Please add Mode of payments and opening balance details."),
+						indicator: 'red'
+					})
+					frappe.utils.play_sound("error");
+					return;
+				}
+				frappe.dom.freeze();
+				return frappe.call("erpnext.selling.page.point_of_sale.point_of_sale.create_opening_voucher", 
+					{ pos_profile, company, balance_details })
+					.then((r) => {
+						frappe.dom.unfreeze();
+						dialog.hide();
+						if (r.message) {
+							this.prepare_app_defaults(r.message);
+						}
+					})
+			},
+			primary_action_label: __('Submit')
+		});
+		dialog.show();
+	}
+
+	prepare_app_defaults(data) {
+		this.pos_opening = data.name;
+		this.company = data.company;
+		this.pos_profile = data.pos_profile;
+		this.pos_opening_time = data.period_start_date;
+
+		frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => {
+			this.allow_negative_stock = flt(message.allow_negative_stock) || false;
+		});
+
+		frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
+			this.customer_groups = profile.customer_groups.map(group => group.customer_group);
+			this.cart.make_customer_selector();
+		});
+
+		this.item_stock_map = {};
+
+		this.make_app();
+	}
+
+	set_opening_entry_status() {
+		this.page.set_title_sub(
+			`<span class="indicator orange">
+				<a class="text-muted" href="#Form/POS%20Opening%20Entry/${this.pos_opening}">
+					Opened at ${moment(this.pos_opening_time).format("Do MMMM, h:mma")}
+				</a>
+			</span>`);
+	}
+
+	make_app() {
+		return frappe.run_serially([
+			() => frappe.dom.freeze(),
+			() => {
+				this.set_opening_entry_status();
+				this.prepare_dom();
+				this.prepare_components();
+				this.prepare_menu();
+			},
+			() => this.make_new_invoice(),
+			() => frappe.dom.unfreeze(),
+			() => this.page.set_title(__('Point of Sale Beta')),
+		]);
+	}
+
+	prepare_dom() {
+		this.wrapper.append(`
+			<div class="app grid grid-cols-10 pt-8 gap-6"></div>`
+		);
+
+		this.$components_wrapper = this.wrapper.find('.app');
+	}
+
+	prepare_components() {
+		this.init_item_selector();
+		this.init_item_details();
+		this.init_item_cart();
+		this.init_payments();
+		this.init_recent_order_list();
+		this.init_order_summary();
+	}
+
+	prepare_menu() {
+		var me = this;
+		this.page.clear_menu();
+
+		this.page.add_menu_item(__("Form View"), function () {
+			frappe.model.sync(me.frm.doc);
+			frappe.set_route("Form", me.frm.doc.doctype, me.frm.doc.name);
+		});
+
+		this.page.add_menu_item(__("Toggle Recent Orders"), () => {
+			const show = this.recent_order_list.$component.hasClass('d-none');
+			this.toggle_recent_order_list(show);
+		});
+
+		this.page.add_menu_item(__("Save as Draft"), this.save_draft_invoice.bind(this));
+
+		frappe.ui.keys.on("ctrl+s", this.save_draft_invoice.bind(this));
+
+		this.page.add_menu_item(__('Close the POS'), this.close_pos.bind(this));
+
+		frappe.ui.keys.on("shift+ctrl+s", this.close_pos.bind(this));
+	}
+
+	save_draft_invoice() {
+		if (!this.$components_wrapper.is(":visible")) return;
+
+		if (this.frm.doc.items.length == 0) {
+			frappe.show_alert({
+				message:__("You must add atleast one item to save it as draft."), 
+				indicator:'red'
+			});
+			frappe.utils.play_sound("error");
+			return;
+		}
+
+		this.frm.save(undefined, undefined, undefined, () => {
+			frappe.show_alert({
+				message:__("There was an error saving the document."), 
+				indicator:'red'
+			});
+			frappe.utils.play_sound("error");
+		}).then(() => {
+			frappe.run_serially([
+				() => frappe.dom.freeze(),
+				() => this.make_new_invoice(),
+				() => frappe.dom.unfreeze(),
+			]);
+		})
+	}
+
+	close_pos() {
+		if (!this.$components_wrapper.is(":visible")) return;
+
+		let voucher = frappe.model.get_new_doc('POS Closing Entry');
+		voucher.pos_profile = this.frm.doc.pos_profile;
+		voucher.user = frappe.session.user;
+		voucher.company = this.frm.doc.company;
+		voucher.pos_opening_entry = this.pos_opening;
+		voucher.period_end_date = frappe.datetime.now_datetime();
+		voucher.posting_date = frappe.datetime.now_date();
+		frappe.set_route('Form', 'POS Closing Entry', voucher.name);
+	}
+
+	init_item_selector() {
+		this.item_selector = new erpnext.PointOfSale.ItemSelector({
+			wrapper: this.$components_wrapper,
+			pos_profile: this.pos_profile,
+			events: {
+				item_selected: args => this.on_cart_update(args),
+
+				get_frm: () => this.frm || {},
+
+				get_allowed_item_group: () => this.item_groups
+			}
+		})
+	}
+
+	init_item_cart() {
+		this.cart = new erpnext.PointOfSale.ItemCart({
+			wrapper: this.$components_wrapper,
+			events: {
+				get_frm: () => this.frm,
+
+				cart_item_clicked: (item_code, batch_no, uom) => {
+					const item_row = this.frm.doc.items.find(
+						i => i.item_code === item_code 
+							&& i.uom === uom
+							&& (!batch_no || (batch_no && i.batch_no === batch_no))
+					);
+					this.item_details.toggle_item_details_section(item_row);
+				},
+
+				numpad_event: (value, action) => this.update_item_field(value, action),
+
+				checkout: () => this.payment.checkout(),
+
+				edit_cart: () => this.payment.edit_cart(),
+
+				customer_details_updated: (details) => {
+					this.customer_details = details;
+					// will add/remove LP payment method
+					this.payment.render_loyalty_points_payment_mode();
+				},
+
+				get_allowed_customer_group: () => this.customer_groups
+			}
+		})
+	}
+
+	init_item_details() {
+		this.item_details = new erpnext.PointOfSale.ItemDetails({
+			wrapper: this.$components_wrapper,
+			events: {
+				get_frm: () => this.frm,
+
+				toggle_item_selector: (minimize) => {
+					this.item_selector.resize_selector(minimize);
+					this.cart.toggle_numpad(minimize);
+				},
+
+				form_updated: async (cdt, cdn, fieldname, value) => {
+					const item_row = frappe.model.get_doc(cdt, cdn);
+					if (item_row && item_row[fieldname] != value) {
+
+						if (fieldname === 'qty' && flt(value) == 0) {
+							this.remove_item_from_cart();
+							return;
+						}
+
+						const { item_code, batch_no, uom } = this.item_details.current_item;
+						const event = {
+							field: fieldname,
+							value,
+							item: { item_code, batch_no, uom }
+						}
+						return this.on_cart_update(event)
+					}
+				},
+
+				item_field_focused: (fieldname) => {
+					this.cart.toggle_numpad_field_edit(fieldname);
+				},
+				set_value_in_current_cart_item: (selector, value) => {
+					this.cart.update_selector_value_in_cart_item(selector, value, this.item_details.current_item);
+				},
+				clone_new_batch_item_in_frm: (batch_serial_map, current_item) => {
+					// called if serial nos are 'auto_selected' and if those serial nos belongs to multiple batches
+					// for each unique batch new item row is added in the form & cart
+					Object.keys(batch_serial_map).forEach(batch => {
+						const { item_code, batch_no } = current_item;
+						const item_to_clone = this.frm.doc.items.find(i => i.item_code === item_code && i.batch_no === batch_no);
+						const new_row = this.frm.add_child("items", { ...item_to_clone });
+						// update new serialno and batch
+						new_row.batch_no = batch;
+						new_row.serial_no = batch_serial_map[batch].join(`\n`);
+						new_row.qty = batch_serial_map[batch].length;
+						this.frm.doc.items.forEach(row => {
+							if (item_code === row.item_code) {
+								this.update_cart_html(row);
+							}
+						});
+					})
+				},
+				remove_item_from_cart: () => this.remove_item_from_cart(),
+				get_item_stock_map: () => this.item_stock_map,
+				close_item_details: () => {
+					this.item_details.toggle_item_details_section(undefined);
+					this.cart.prev_action = undefined;
+					this.cart.toggle_item_highlight();
+				},
+				get_available_stock: (item_code, warehouse) => this.get_available_stock(item_code, warehouse)
+			}
+		});
+	}
+
+	init_payments() {
+		this.payment = new erpnext.PointOfSale.Payment({
+			wrapper: this.$components_wrapper,
+			events: {
+				get_frm: () => this.frm || {},
+
+				get_customer_details: () => this.customer_details || {},
+
+				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');
+					} else {
+						this.item_selector.$component.removeClass('d-none');
+					}
+				},
+
+				submit_invoice: () => {
+					this.frm.savesubmit()
+						.then((r) => {
+							// this.set_invoice_status();
+							this.toggle_components(false);
+							this.order_summary.toggle_component(true);
+							this.order_summary.load_summary_of(this.frm.doc, true);
+							frappe.show_alert({
+								indicator: 'green',
+								message: __(`POS invoice ${r.doc.name} created succesfully`)
+							});
+						});
+				}
+			}
+		});
+	}
+
+	init_recent_order_list() {
+		this.recent_order_list = new erpnext.PointOfSale.PastOrderList({
+			wrapper: this.$components_wrapper,
+			events: {
+				open_invoice_data: (name) => {
+					frappe.db.get_doc('POS Invoice', name).then((doc) => {
+						this.order_summary.load_summary_of(doc);
+					});
+				},
+				reset_summary: () => this.order_summary.show_summary_placeholder()
+			}
+		})
+	}
+
+	init_order_summary() {
+		this.order_summary = new erpnext.PointOfSale.PastOrderSummary({
+			wrapper: this.$components_wrapper,
+			events: {
+				get_frm: () => this.frm,
+
+				process_return: (name) => {
+					this.recent_order_list.toggle_component(false);
+					frappe.db.get_doc('POS Invoice', name).then((doc) => {
+						frappe.run_serially([
+							() => this.make_return_invoice(doc),
+							() => this.cart.load_invoice(),
+							() => this.item_selector.toggle_component(true)
+						]);
+					});
+				},
+				edit_order: (name) => {
+					this.recent_order_list.toggle_component(false);
+					frappe.run_serially([
+						() => this.frm.refresh(name),
+						() => this.cart.load_invoice(),
+						() => this.item_selector.toggle_component(true)
+					]);
+				},
+				new_order: () => {
+					frappe.run_serially([
+						() => frappe.dom.freeze(),
+						() => this.make_new_invoice(),
+						() => this.item_selector.toggle_component(true),
+						() => frappe.dom.unfreeze(),
+					]);
+				}
+			}
+		})
+	}
+
+	
+
+	toggle_recent_order_list(show) {
+		this.toggle_components(!show);
+		this.recent_order_list.toggle_component(show);
+		this.order_summary.toggle_component(show);
+	}
+
+	toggle_components(show) {
+		this.cart.toggle_component(show);
+		this.item_selector.toggle_component(show);
+
+		// do not show item details or payment if recent order is toggled off
+		!show ? (this.item_details.toggle_component(false) || this.payment.toggle_component(false)) : '';
+	}
+
+	make_new_invoice() {
+		return frappe.run_serially([
+			() => this.make_sales_invoice_frm(),
+			() => this.set_pos_profile_data(),
+			() => this.set_pos_profile_status(),
+			() => this.cart.load_invoice(),
+		]);
+	}
+
+	make_sales_invoice_frm() {
+		const doctype = 'POS Invoice';
+		return new Promise(resolve => {
+			if (this.frm) {
+				this.frm = this.get_new_frm(this.frm);
+				this.frm.doc.items = [];
+				this.frm.doc.is_pos = 1
+				resolve();
+			} else {
+				frappe.model.with_doctype(doctype, () => {
+					this.frm = this.get_new_frm();
+					this.frm.doc.items = [];
+					this.frm.doc.is_pos = 1
+					resolve();
+				});
+			}
+		});
+	}
+
+	get_new_frm(_frm) {
+		const doctype = 'POS Invoice';
+		const page = $('<div>');
+		const frm = _frm || new frappe.ui.form.Form(doctype, page, false);
+		const name = frappe.model.make_new_doc_and_get_name(doctype, true);
+		frm.refresh(name);
+
+		return frm;
+	}
+
+	async make_return_invoice(doc) {
+		frappe.dom.freeze();
+		this.frm = this.get_new_frm(this.frm);
+		this.frm.doc.items = [];
+		const res = await frappe.call({
+			method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return",
+			args: {
+				'source_name': doc.name,
+				'target_doc': this.frm.doc
+			}
+		});
+		frappe.model.sync(res.message);
+		await this.set_pos_profile_data();
+		frappe.dom.unfreeze();
+	}
+
+	set_pos_profile_data() {
+		if (this.company && !this.frm.doc.company) this.frm.doc.company = this.company;
+		if (this.pos_profile && !this.frm.doc.pos_profile) this.frm.doc.pos_profile = this.pos_profile;
+		if (!this.frm.doc.company) return;
+
+		return new Promise(resolve => {
+			return this.frm.call({
+				doc: this.frm.doc,
+				method: "set_missing_values",
+			}).then((r) => {
+				if(!r.exc) {
+					if (!this.frm.doc.pos_profile) {
+						frappe.dom.unfreeze();
+						this.raise_exception_for_pos_profile();
+					}
+					this.frm.trigger("update_stock");
+					this.frm.trigger('calculate_taxes_and_totals');
+					if(this.frm.doc.taxes_and_charges) this.frm.script_manager.trigger("taxes_and_charges");
+					frappe.model.set_default_values(this.frm.doc);
+					if (r.message) {
+						this.frm.pos_print_format = r.message.print_format || "";
+						this.frm.meta.default_print_format = r.message.print_format || "";
+						this.frm.allow_edit_rate = r.message.allow_edit_rate;
+						this.frm.allow_edit_discount = r.message.allow_edit_discount;
+						this.frm.doc.campaign = r.message.campaign;
+					}
+				}
+				resolve();
+			});
+		});
+	}
+
+	raise_exception_for_pos_profile() {
+		setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000);
+		frappe.throw(__("POS Profile is required to use Point-of-Sale"));
+	}
+
+	set_invoice_status() {
+		const [status, indicator] = frappe.listview_settings["POS Invoice"].get_indicator(this.frm.doc);
+		this.page.set_indicator(__(`${status}`), indicator);
+	}
+
+	set_pos_profile_status() {
+		this.page.set_indicator(__(`${this.pos_profile}`), "blue");
+	}
+
+	async on_cart_update(args) {
+		frappe.dom.freeze();
+		try {
+			let { field, value, item } = args;
+			const { item_code, batch_no, serial_no, uom } = item;
+			let item_row = this.get_item_from_frm(item_code, batch_no, uom);
+
+			const item_selected_from_selector = field === 'qty' && value === "+1"
+
+			if (item_row) {
+				item_selected_from_selector && (value = item_row.qty + flt(value))
+
+				field === 'qty' && (value = flt(value));
+
+				if (field === 'qty' && value > 0 && !this.allow_negative_stock)
+					await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
+				
+				if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) {
+					await frappe.model.set_value(item_row.doctype, item_row.name, field, value);
+					this.update_cart_html(item_row);
+				}
+
+			} else {
+				if (!this.frm.doc.customer) {
+					frappe.dom.unfreeze();
+					frappe.show_alert({
+						message: __('You must select a customer before adding an item.'),
+						indicator: 'orange'
+					});
+					frappe.utils.play_sound("error");
+					return;
+				}
+				item_selected_from_selector && (value = flt(value))
+
+				const args = { item_code, batch_no, [field]: value };
+
+				if (serial_no) args['serial_no'] = serial_no;
+
+				if (field === 'serial_no') args['qty'] = value.split(`\n`).length || 0;
+
+				item_row = this.frm.add_child('items', args);
+
+				if (field === 'qty' && value !== 0 && !this.allow_negative_stock)
+					await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
+
+				await this.trigger_new_item_events(item_row);
+
+				this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
+				this.update_cart_html(item_row);
+			}	
+		} catch (error) {
+			console.log(error);
+		} finally {
+			frappe.dom.unfreeze();
+		}
+	}
+
+	get_item_from_frm(item_code, batch_no, uom) {
+		const has_batch_no = batch_no;
+		return this.frm.doc.items.find(
+			i => i.item_code === item_code 
+				&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
+				&& (i.uom === uom)
+		);
+	}
+
+	edit_item_details_of(item_row) {
+		this.item_details.toggle_item_details_section(item_row);
+	}
+
+	is_current_item_being_edited(item_row) {
+		const { item_code, batch_no } = this.item_details.current_item;
+
+		return item_code !== item_row.item_code || batch_no != item_row.batch_no ? false : true;
+	}
+
+	update_cart_html(item_row, remove_item) {
+		this.cart.update_item_html(item_row, remove_item);
+		this.cart.update_totals_section(this.frm);
+	}
+
+	check_serial_batch_selection_needed(item_row) {
+		// right now item details is shown for every type of item.
+		// if item details is not shown for every item then this fn will be needed
+		const serialized = item_row.has_serial_no;
+		const batched = item_row.has_batch_no;
+		const no_serial_selected = !item_row.serial_no;
+		const no_batch_selected = !item_row.batch_no;
+
+		if ((serialized && no_serial_selected) || (batched && no_batch_selected) || 
+			(serialized && batched && (no_batch_selected || no_serial_selected))) {
+			return true;
+		}
+		return false;
+	}
+
+	async trigger_new_item_events(item_row) {
+		await this.frm.script_manager.trigger('item_code', item_row.doctype, item_row.name)
+		await this.frm.script_manager.trigger('qty', item_row.doctype, item_row.name)
+	}
+
+	async check_stock_availability(item_row, qty_needed, warehouse) {
+		const available_qty = (await this.get_available_stock(item_row.item_code, warehouse)).message;
+
+		frappe.dom.unfreeze();
+		if (!(available_qty > 0)) {
+			frappe.model.clear_doc(item_row.doctype, item_row.name);
+			frappe.throw(__(`Item Code: ${item_row.item_code.bold()} is not available under warehouse ${warehouse.bold()}.`))
+		} else if (available_qty < qty_needed) {
+			frappe.show_alert({
+				message: __(`Stock quantity not enough for Item Code: ${item_row.item_code.bold()} under warehouse ${warehouse.bold()}. 
+					Available quantity ${available_qty.toString().bold()}.`),
+				indicator: 'orange'
+			});
+			frappe.utils.play_sound("error");
+			this.item_details.qty_control.set_value(flt(available_qty));
+		}
+		frappe.dom.freeze();
+	}
+
+	get_available_stock(item_code, warehouse) {
+		const me = this;
+		return frappe.call({
+			method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.get_stock_availability",
+			args: {
+				'item_code': item_code,
+				'warehouse': warehouse,
+			},
+			callback(res) {
+				if (!me.item_stock_map[item_code])
+					me.item_stock_map[item_code] = {}
+				me.item_stock_map[item_code][warehouse] = res.message;
+			}
+		});
+	}
+
+	update_item_field(value, field_or_action) {
+		if (field_or_action === 'checkout') {
+			this.item_details.toggle_item_details_section(undefined);
+		} else if (field_or_action === 'remove') {
+			this.remove_item_from_cart();
+		} else {
+			const field_control = this.item_details[`${field_or_action}_control`];
+			if (!field_control) return;
+			field_control.set_focus();
+			value != "" && field_control.set_value(value);
+		}
+	}
+
+	remove_item_from_cart() {
+		frappe.dom.freeze();
+		const { doctype, name, current_item } = this.item_details;
+
+		frappe.model.set_value(doctype, name, 'qty', 0);
+
+		this.frm.script_manager.trigger('qty', doctype, name).then(() => {
+			frappe.model.clear_doc(doctype, name);
+			this.update_cart_html(current_item, true);
+			this.item_details.toggle_item_details_section(undefined);
+			frappe.dom.unfreeze();
+		})
+	}
+}
+
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
new file mode 100644
index 0000000..c23a6ad
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -0,0 +1,951 @@
+erpnext.PointOfSale.ItemCart = class {
+    constructor({ wrapper, events }) {
+		this.wrapper = wrapper;
+		this.events = events;
+        this.customer_info = undefined;
+        
+        this.init_component();
+    }
+    
+    init_component() {
+        this.prepare_dom();
+        this.init_child_components();
+		this.bind_events();
+		this.attach_shortcuts();
+    }
+
+    prepare_dom() {
+		this.wrapper.append(
+            `<section class="col-span-4 flex flex-col shadow rounded item-cart bg-white mx-h-70 h-100"></section>`
+        )
+
+        this.$component = this.wrapper.find('.item-cart');
+    }
+
+    init_child_components() {
+        this.init_customer_selector();
+        this.init_cart_components();
+    }
+
+    init_customer_selector() {
+		this.$component.append(
+            `<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
+        )
+		this.$customer_section = this.$component.find('.customer-section');
+	}
+	
+	reset_customer_selector() {
+		const frm = this.events.get_frm();
+		frm.set_value('customer', '');
+		this.$customer_section.removeClass('border pr-4 pl-4');
+		this.make_customer_selector();
+		this.customer_field.set_focus();
+	}
+    
+    init_cart_components() {
+        this.$component.append(
+			`<div class="cart-container flex flex-col items-center rounded flex-1 relative">
+				<div class="absolute flex flex-col p-8 pt-0 w-full h-full">
+					<div class="flex text-grey cart-header pt-2 pb-2 p-4 mt-2 mb-2 w-full f-shrink-0">
+						<div class="flex-1">Item</div>
+						<div class="mr-4">Qty</div>
+						<div class="rate-list-header mr-1 text-right">Amount</div>
+					</div>
+					<div class="cart-items-section flex flex-col flex-1 scroll-y rounded w-full"></div>
+					<div class="cart-totals-section flex flex-col w-full mt-4 f-shrink-0"></div>
+					<div class="numpad-section flex flex-col mt-4 d-none w-full p-8 pt-0 pb-0 f-shrink-0"></div>
+				</div>		
+            </div>`
+        );
+		this.$cart_container = this.$component.find('.cart-container');
+
+		this.make_cart_totals_section();
+		this.make_cart_items_section();
+        this.make_cart_numpad();
+    }
+
+    make_cart_items_section() {
+        this.$cart_header = this.$component.find('.cart-header');
+        this.$cart_items_wrapper = this.$component.find('.cart-items-section');
+
+		this.make_no_items_placeholder();
+    }
+    
+    make_no_items_placeholder() {
+		this.$cart_header.addClass('d-none');
+		this.$cart_items_wrapper.html(
+			`<div class="no-item-wrapper flex items-center h-18">
+				<div class="flex-1 text-center text-grey">No items in cart</div>
+			</div>`
+		)
+		this.$cart_items_wrapper.addClass('mt-4 border-grey border-dashed');
+	}
+
+    make_cart_totals_section() {
+        this.$totals_section = this.$component.find('.cart-totals-section');
+
+		this.$totals_section.append(
+			`<div class="add-discount flex items-center pt-4 pb-4 pr-4 pl-4 text-grey pointer no-select d-none">
+				+ Add Discount
+			</div>
+			<div class="border border-grey rounded">
+				<div class="net-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
+					<div class="flex flex-col">
+						<div class="text-md text-dark-grey text-bold">Net Total</div>
+					</div>
+					<div class="flex flex-col text-right">
+						<div class="text-md text-dark-grey text-bold">0.00</div>
+					</div>
+				</div>
+				<div class="taxes"></div>
+				<div class="grand-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
+					<div class="flex flex-col">
+						<div class="text-md text-dark-grey text-bold">Grand Total</div>
+					</div>
+					<div class="flex flex-col text-right">
+						<div class="text-md text-dark-grey text-bold">0.00</div>
+					</div>
+				</div>
+				<div class="checkout-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer rounded-b text-md text-bold">
+					Checkout
+				</div>
+				<div class="edit-cart-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer d-none text-md text-bold">
+					Edit Cart
+				</div>
+			</div>`
+		)
+
+		this.$add_discount_elem = this.$component.find(".add-discount");
+    }
+    
+    make_cart_numpad() {
+		this.$numpad_section = this.$component.find('.numpad-section');
+
+		this.number_pad = new erpnext.PointOfSale.NumberPad({
+			wrapper: this.$numpad_section,
+			events: {
+				numpad_event: this.on_numpad_event.bind(this)
+			},
+			cols: 5,
+			keys: [
+				[ 1, 2, 3, 'Quantity' ],
+				[ 4, 5, 6, 'Discount' ],
+				[ 7, 8, 9, 'Rate' ],
+				[ '.', 0, 'Delete', 'Remove' ]
+			],
+			css_classes: [
+				[ '', '', '', 'col-span-2' ],
+				[ '', '', '', 'col-span-2' ],
+				[ '', '', '', 'col-span-2' ],
+				[ '', '', '', 'col-span-2 text-bold text-danger' ]
+			],
+			fieldnames_map: { 'Quantity': 'qty', 'Discount': 'discount_percentage' }
+		})
+
+		this.$numpad_section.prepend(
+			`<div class="flex mb-2 justify-between">
+				<span class="numpad-net-total"></span>
+				<span class="numpad-grand-total"></span>
+			</div>`
+		)
+
+		this.$numpad_section.append(
+			`<div class="numpad-btn checkout-btn flex items-center justify-center h-16 pr-8 pl-8 bg-primary
+				text-center text-white no-select pointer rounded text-md text-bold mt-4" data-button-value="checkout">
+					Checkout
+			</div>`
+		)
+    }
+    
+    bind_events() {
+		const me = this;
+		this.$customer_section.on('click', '.add-remove-customer', function (e) {
+			const customer_info_is_visible = me.$cart_container.hasClass('d-none');
+			customer_info_is_visible ? 
+				me.toggle_customer_info(false) : me.reset_customer_selector();
+		});
+
+		this.$customer_section.on('click', '.customer-header', function(e) {
+			// don't triggger the event if .add-remove-customer btn is clicked which is under .customer-header
+			if ($(e.target).closest('.add-remove-customer').length) return;
+
+			const show = !me.$cart_container.hasClass('d-none');
+			me.toggle_customer_info(show);
+		});
+
+		this.$cart_items_wrapper.on('click', '.cart-item-wrapper', function() {
+			const $cart_item = $(this);
+
+			me.toggle_item_highlight(this);
+
+			const payment_section_hidden = me.$totals_section.find('.edit-cart-btn').hasClass('d-none');
+			if (!payment_section_hidden) {
+				// payment section is visible
+				// edit cart first and then open item details section
+				me.$totals_section.find(".edit-cart-btn").click();
+			}
+
+			const item_code = unescape($cart_item.attr('data-item-code'));
+			const batch_no = unescape($cart_item.attr('data-batch-no'));
+			const uom = unescape($cart_item.attr('data-uom'));
+			me.events.cart_item_clicked(item_code, batch_no, uom);
+			this.numpad_value = '';
+		});
+
+		this.$component.on('click', '.checkout-btn', function() {
+			if (!$(this).hasClass('bg-primary')) return;
+			
+			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;
+
+			if(!this.discount_field || can_edit_discount) this.show_discount_control();
+		});
+
+		frappe.ui.form.on("POS Invoice", "paid_amount", frm => {
+			// called when discount is applied
+			this.update_totals_section(frm);
+		});
+	}
+
+	attach_shortcuts() {
+		for (let row of this.number_pad.keys) {
+			for (let btn of row) {
+				let shortcut_key = `ctrl+${frappe.scrub(String(btn))[0]}`;
+				if (btn === 'Delete') shortcut_key = 'ctrl+backspace';
+				if (btn === 'Remove') shortcut_key = 'shift+ctrl+backspace'
+				if (btn === '.') shortcut_key = 'ctrl+>';
+
+				// to account for fieldname map
+				const fieldname = this.number_pad.fieldnames[btn] ? this.number_pad.fieldnames[btn] : 
+					typeof btn === 'string' ? frappe.scrub(btn) : btn;
+
+				frappe.ui.keys.on(`${shortcut_key}`, () => {
+					const cart_is_visible = this.$component.is(":visible");
+					if (cart_is_visible && this.item_is_selected && this.$numpad_section.is(":visible")) {
+						this.$numpad_section.find(`.numpad-btn[data-button-value="${fieldname}"]`).click();
+					} 
+				})
+			}
+		}
+
+		frappe.ui.keys.on("ctrl+enter", () => {
+			const cart_is_visible = this.$component.is(":visible");
+			const payment_section_hidden = this.$totals_section.find('.edit-cart-btn').hasClass('d-none');
+			if (cart_is_visible && payment_section_hidden) {
+				this.$component.find(".checkout-btn").click();
+			}
+		});
+	}
+	
+	toggle_item_highlight(item) {
+		const $cart_item = $(item);
+		const item_is_highlighted = $cart_item.hasClass("shadow");
+
+		if (!item || item_is_highlighted) {
+			this.item_is_selected = false;
+			this.$cart_container.find('.cart-item-wrapper').removeClass("shadow").css("opacity", "1");
+		} else {
+			$cart_item.addClass("shadow");
+			this.item_is_selected = true;
+			this.$cart_container.find('.cart-item-wrapper').css("opacity", "1");
+			this.$cart_container.find('.cart-item-wrapper').not(item).removeClass("shadow").css("opacity", "0.65");
+		}
+		// highlight with inner shadow
+		// $cart_item.addClass("shadow-inner bg-selected");
+		// me.$cart_container.find('.cart-item-wrapper').not(this).removeClass("shadow-inner bg-selected");
+	}
+
+	make_customer_selector() {
+		this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`);
+		const me = this;
+		const query = { query: 'erpnext.controllers.queries.customer_query' };
+		const allowed_customer_group = this.events.get_allowed_customer_group() || [];
+		if (allowed_customer_group.length) {
+			query.filters = {
+				customer_group: ['in', allowed_customer_group]
+			}
+		}
+		this.customer_field = frappe.ui.form.make_control({
+			df: {
+				label: __('Customer'),
+				fieldtype: 'Link',
+				options: 'Customer',
+				placeholder: __('Search by customer name, phone, email.'),
+				get_query: () => query,
+				onchange: function() {
+					if (this.value) {
+						const frm = me.events.get_frm();
+						frappe.dom.freeze();
+						frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'customer', this.value);
+						frm.script_manager.trigger('customer', frm.doc.doctype, frm.doc.name).then(() => {
+							frappe.run_serially([
+								() => me.fetch_customer_details(this.value),
+								() => me.events.customer_details_updated(me.customer_info),
+								() => me.update_customer_section(),
+								() => me.update_totals_section(),
+								() => frappe.dom.unfreeze()
+							]);
+						})
+					}
+				},
+			},
+			parent: this.$customer_section.find('.customer-search-field'),
+			render_input: true,
+		});
+		this.customer_field.toggle_label(false);
+	}
+	
+	fetch_customer_details(customer) {
+		if (customer) {
+			return new Promise((resolve) => {
+				frappe.db.get_value('Customer', customer, ["email_id", "mobile_no", "image", "loyalty_program"]).then(({ message }) => {
+					const { loyalty_program } = message;
+					// if loyalty program then fetch loyalty points too
+					if (loyalty_program) {
+						frappe.call({
+							method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details_with_points",
+							args: { customer, loyalty_program, "silent": true },
+							callback: (r) => {
+								const { loyalty_points, conversion_factor } = r.message;
+								if (!r.exc) {
+									this.customer_info = { ...message, customer, loyalty_points, conversion_factor };
+									resolve();
+								}
+							}
+						});
+					} else {
+						this.customer_info = { ...message, customer };
+						resolve();
+					}
+				});
+			});
+		} else {
+			return new Promise((resolve) => {
+				this.customer_info = {}
+				resolve();
+			});
+		}
+	}
+
+	show_discount_control() {
+		this.$add_discount_elem.removeClass("pr-4 pl-4");
+		this.$add_discount_elem.html(
+			`<div class="add-dicount-field flex flex-1 items-center"></div>
+			<div class="submit-field flex items-center"></div>`
+		);
+		const me = this;
+
+		this.discount_field = frappe.ui.form.make_control({
+			df: {
+				label: __('Discount'),
+				fieldtype: 'Data',
+				placeholder: __('Enter discount percentage.'),
+				onchange: function() {
+					if (this.value || this.value == 0) {
+						const frm = me.events.get_frm();
+						frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', this.value);
+						me.hide_discount_control(this.value);
+					}
+				},
+			},
+			parent: this.$add_discount_elem.find('.add-dicount-field'),
+			render_input: true,
+		});
+		this.discount_field.toggle_label(false);
+		this.discount_field.set_focus();
+	}
+
+	hide_discount_control(discount) {
+		this.$add_discount_elem.addClass('pr-4 pl-4');
+		this.$add_discount_elem.html(
+			`<svg class="mr-2" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" 
+				stroke-linecap="round" stroke-linejoin="round">
+				<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
+			</svg> 
+			<div class="edit-discount p-1 pr-3 pl-3 text-dark-grey rounded w-fit bg-green-200 mb-2">
+				${String(discount).bold()}% off
+			</div>
+			`
+		);
+	}
+    
+    update_customer_section() {
+		const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
+
+		if (customer) {
+			this.$customer_section.addClass('border pr-4 pl-4').html(
+				`<div class="customer-details flex flex-col">
+					<div class="customer-header flex items-center rounded h-18 pointer">
+						${get_customer_image()}
+						<div class="customer-name flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
+							<div class="text-md text-dark-grey text-bold">${customer}</div>
+							${get_customer_description()}
+						</div>
+						<div class="f-shrink-0 add-remove-customer flex items-center pointer" data-customer="${escape(customer)}">
+							<svg width="32" height="32" viewBox="0 0 14 14" fill="none">
+								<path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
+							</svg>
+						</div>
+					</div>
+				</div>`
+			);
+		} else {
+            // reset customer selector
+			this.reset_customer_selector();
+		}
+
+		function get_customer_description() {
+			if (!email_id && !mobile_no) {
+				return `<div class="text-grey-200 italic">Click to add email / phone</div>`
+			} else if (email_id && !mobile_no) {
+				return `<div class="text-grey">${email_id}</div>`
+			} else if (mobile_no && !email_id) {
+				return `<div class="text-grey">${mobile_no}</div>`
+			} else {
+				return `<div class="text-grey">${email_id} | ${mobile_no}</div>`
+			}
+		}
+
+		function get_customer_image() {
+			if (image) {
+				return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
+							<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
+						</div>`
+			} else {
+				return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200 text-md">
+							${frappe.get_abbr(customer)}
+						</div>`
+			}
+		}
+    }
+    
+    update_totals_section(frm) {
+		if (!frm) frm = this.events.get_frm();
+
+		this.render_net_total(frm.doc.base_net_total);
+		this.render_grand_total(frm.doc.base_grand_total);
+
+		const taxes = frm.doc.taxes.map(t => { return { description: t.description, rate: t.rate }})
+		this.render_taxes(frm.doc.base_total_taxes_and_charges, taxes);
+    }
+    
+    render_net_total(value) {
+		const currency = this.events.get_frm().doc.currency;
+		this.$totals_section.find('.net-total').html(
+			`<div class="flex flex-col">
+				<div class="text-md text-dark-grey text-bold">Net Total</div>
+			</div>
+			<div class="flex flex-col text-right">
+				<div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
+			</div>`
+		)
+
+		this.$numpad_section.find('.numpad-net-total').html(`Net Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
+    }
+    
+    render_grand_total(value) {
+		const currency = this.events.get_frm().doc.currency;
+		this.$totals_section.find('.grand-total').html(
+			`<div class="flex flex-col">
+				<div class="text-md text-dark-grey text-bold">Grand Total</div>
+			</div>
+			<div class="flex flex-col text-right">
+				<div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
+			</div>`
+		)
+
+		this.$numpad_section.find('.numpad-grand-total').html(`Grand Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
+	}
+
+	render_taxes(value, taxes) {
+		if (taxes.length) {
+			const currency = this.events.get_frm().doc.currency;
+			this.$totals_section.find('.taxes').html(
+				`<div class="flex items-center justify-between h-16 pr-8 pl-8 border-b-grey">
+					<div class="flex">
+						<div class="text-md text-dark-grey text-bold w-fit">Tax Charges</div>
+						<div class="flex ml-6 text-dark-grey">
+						${	
+							taxes.map((t, i) => {
+								let margin_left = '';
+								if (i !== 0) margin_left = 'ml-2';
+								return `<span class="border-grey p-1 pl-2 pr-2 rounded ${margin_left}">${t.description}</span>`
+							}).join('')
+						}
+						</div>
+					</div>
+					<div class="flex flex-col text-right">
+						<div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
+					</div>
+				</div>`
+			)
+		} else {
+			this.$totals_section.find('.taxes').html('')
+		}
+    }
+
+    get_cart_item({ item_code, batch_no, uom }) {
+		const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
+		const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
+		const uom_attr = `[data-uom=${escape(uom)}]`;
+
+        const item_selector = batch_no ? 
+            `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
+            
+        return this.$cart_items_wrapper.find(item_selector);
+    }
+    
+    update_item_html(item, remove_item) {
+		const $item = this.get_cart_item(item);
+
+		if (remove_item) {
+			$item && $item.remove();
+		} else {
+			const { item_code, batch_no, uom } = item;
+			const search_field = batch_no ? 'batch_no' : 'item_code';
+			const search_value = batch_no || item_code;
+			const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom);
+			
+			this.render_cart_item(item_row, $item);
+		}
+
+		const no_of_cart_items = this.$cart_items_wrapper.children().length;
+		no_of_cart_items > 0 && this.highlight_checkout_btn(no_of_cart_items > 0);
+        
+		this.update_empty_cart_section(no_of_cart_items);
+	}
+    
+    render_cart_item(item_data, $item_to_update) {
+		const currency = this.events.get_frm().doc.currency;
+		const me = this;
+		
+        if (!$item_to_update.length) {
+            this.$cart_items_wrapper.append(
+                `<div class="cart-item-wrapper flex items-center h-18 pr-4 pl-4 rounded border-grey pointer no-select" 
+						data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
+						data-batch-no="${escape(item_data.batch_no || '')}">
+                </div>`
+            )
+            $item_to_update = this.get_cart_item(item_data);
+        }
+
+		$item_to_update.html(
+			`<div class="flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
+                <div class="text-md text-dark-grey text-bold">
+                    ${item_data.item_name}
+                </div>
+                ${get_description_html()}
+            </div>
+                ${get_rate_discount_html()}
+            </div>`
+		)
+
+		set_dynamic_rate_header_width();
+		this.scroll_to_item($item_to_update);
+
+		function set_dynamic_rate_header_width() {
+			const rate_cols = Array.from(me.$cart_items_wrapper.find(".rate-col"));
+			me.$cart_header.find(".rate-list-header").css("width", "");
+			me.$cart_items_wrapper.find(".rate-col").css("width", "");
+			let max_width = rate_cols.reduce((max_width, elm) => {
+				if ($(elm).width() > max_width)
+					max_width = $(elm).width();
+				return max_width;
+			}, 0);
+
+			max_width += 1;
+			if (max_width == 1) max_width = "";
+
+			me.$cart_header.find(".rate-list-header").css("width", max_width);
+			me.$cart_items_wrapper.find(".rate-col").css("width", max_width);
+		}
+        
+		function get_rate_discount_html() {
+			if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) {
+				return `
+					<div class="flex f-shrink-0 ml-4 items-center">
+						<div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
+							<span>${item_data.qty || 0}</span>
+						</div>
+						<div class="rate-col flex flex-col f-shrink-0 text-right">
+							<div class="text-md text-dark-grey text-bold">${format_currency(item_data.amount, currency)}</div>
+							<div class="text-md-0 text-dark-grey">${format_currency(item_data.rate, currency)}</div>
+						</div>
+					</div>`
+			} else {
+				return `
+					<div class="flex f-shrink-0 ml-4 text-right">
+						<div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
+							<span>${item_data.qty || 0}</span>
+						</div>
+						<div class="rate-col flex flex-col f-shrink-0 text-right">
+							<div class="text-md text-dark-grey text-bold">${format_currency(item_data.rate, currency)}</div>
+						</div>
+					</div>`
+			}
+		}
+
+		function get_description_html() {
+			if (item_data.description) {
+				if (item_data.description.indexOf('<div>') != -1) {
+					try {
+						item_data.description = $(item_data.description).text();
+					} catch (error) {
+						item_data.description = item_data.description.replace(/<div>/g, ' ').replace(/<\/div>/g, ' ').replace(/ +/g, ' ');
+					}
+				}
+				item_data.description = frappe.ellipsis(item_data.description, 45);
+				return `<div class="text-grey">${item_data.description}</div>`
+			}
+			return ``;
+		}
+	}
+
+	scroll_to_item($item) {
+		if ($item.length === 0) return;
+		const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
+		this.$cart_items_wrapper.animate({ scrollTop });
+	}
+	
+	update_selector_value_in_cart_item(selector, value, item) {
+		const $item_to_update = this.get_cart_item(item);
+		$item_to_update.attr(`data-${selector}`, value);
+	}
+
+    toggle_checkout_btn(show_checkout) {
+		if (show_checkout) {
+			this.$totals_section.find('.checkout-btn').removeClass('d-none');
+			this.$totals_section.find('.edit-cart-btn').addClass('d-none');
+		} else {
+			this.$totals_section.find('.checkout-btn').addClass('d-none');
+			this.$totals_section.find('.edit-cart-btn').removeClass('d-none');
+		}
+	}
+
+    highlight_checkout_btn(toggle) {
+		const has_primary_class = this.$totals_section.find('.checkout-btn').hasClass('bg-primary');
+		if (toggle && !has_primary_class) {
+			this.$totals_section.find('.checkout-btn').addClass('bg-primary text-white text-lg');
+		} else if (!toggle && has_primary_class) {
+			this.$totals_section.find('.checkout-btn').removeClass('bg-primary text-white text-lg');
+		}
+	}
+    
+    update_empty_cart_section(no_of_cart_items) {
+		const $no_item_element = this.$cart_items_wrapper.find('.no-item-wrapper');
+
+		// if cart has items and no item is present
+		no_of_cart_items > 0 && $no_item_element && $no_item_element.remove()
+			&& this.$cart_items_wrapper.removeClass('mt-4 border-grey border-dashed') && this.$cart_header.removeClass('d-none');
+
+		no_of_cart_items === 0 && !$no_item_element.length && this.make_no_items_placeholder();
+    }
+    
+    on_numpad_event($btn) {
+		const current_action = $btn.attr('data-button-value');
+		const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action);
+
+		this.highlight_numpad_btn($btn, current_action);
+
+        const action_is_pressed_twice = this.prev_action === current_action;
+        const first_click_event = !this.prev_action;
+        const field_to_edit_changed = this.prev_action && this.prev_action != current_action;
+
+		if (action_is_field_edit) {
+
+			if (first_click_event || field_to_edit_changed) {
+                this.prev_action = current_action;
+			} else if (action_is_pressed_twice) {
+				this.prev_action = undefined;
+			}
+            this.numpad_value = '';
+            
+		} else if (current_action === 'checkout') {
+			this.prev_action = undefined;
+			this.toggle_item_highlight();
+			this.events.numpad_event(undefined, current_action);
+			return;
+		} else if (current_action === 'remove') {
+			this.prev_action = undefined;
+			this.toggle_item_highlight();
+			this.events.numpad_event(undefined, current_action);
+			return;
+		} else {
+			this.numpad_value = current_action === 'delete' ? this.numpad_value.slice(0, -1) : this.numpad_value + current_action;
+			this.numpad_value = this.numpad_value || 0;
+		}
+
+        const first_click_event_is_not_field_edit = !action_is_field_edit && first_click_event;
+
+		if (first_click_event_is_not_field_edit) {
+			frappe.show_alert({
+				indicator: 'red',
+				message: __('Please select a field to edit from numpad')
+			});
+			frappe.utils.play_sound("error");
+			return;
+		}
+		
+		if (flt(this.numpad_value) > 100 && this.prev_action === 'discount_percentage') {
+			frappe.show_alert({
+				message: __('Discount cannot be greater than 100%'),
+				indicator: 'orange'
+			});
+			frappe.utils.play_sound("error");
+			this.numpad_value = current_action;
+		}
+
+        this.events.numpad_event(this.numpad_value, this.prev_action);
+    }
+    
+    highlight_numpad_btn($btn, curr_action) {
+        const curr_action_is_highlighted = $btn.hasClass('shadow-inner');
+        const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action);
+
+        if (!curr_action_is_highlighted) {
+            $btn.addClass('shadow-inner bg-selected');
+        }
+        if (this.prev_action === curr_action && curr_action_is_highlighted) {
+            // if Qty is pressed twice
+            $btn.removeClass('shadow-inner bg-selected');
+        }
+        if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) {
+            // Order: Qty -> Rate then remove Qty highlight
+            const prev_btn = $(`[data-button-value='${this.prev_action}']`);
+            prev_btn.removeClass('shadow-inner bg-selected');
+        }
+        if (!curr_action_is_action || curr_action === 'done') {
+            // if numbers are clicked
+            setTimeout(() => {
+                $btn.removeClass('shadow-inner bg-selected');
+            }, 100);
+        }
+    }
+
+    toggle_numpad(show) {
+		if (show) {
+			this.$totals_section.addClass('d-none');
+			this.$numpad_section.removeClass('d-none');
+		} else {
+			this.$totals_section.removeClass('d-none');
+			this.$numpad_section.addClass('d-none');
+		}
+		this.reset_numpad();
+	}
+
+	reset_numpad() {
+		this.numpad_value = '';
+		this.prev_action = undefined;
+		this.$numpad_section.find('.shadow-inner').removeClass('shadow-inner bg-selected');
+	}
+
+	toggle_numpad_field_edit(fieldname) {
+		if (['qty', 'discount_percentage', 'rate'].includes(fieldname)) {
+			this.$numpad_section.find(`[data-button-value="${fieldname}"]`).click();
+		}
+	}
+
+	toggle_customer_info(show) {
+		if (show) {
+			this.$cart_container.addClass('d-none')
+			this.$customer_section.addClass('flex-1 scroll-y').removeClass('mb-0 border pr-4 pl-4')
+			this.$customer_section.find('.icon').addClass('w-24 h-24 text-2xl').removeClass('w-12 h-12 text-md')
+			this.$customer_section.find('.customer-header').removeClass('h-18');
+			this.$customer_section.find('.customer-details').addClass('sticky z-100 bg-white');
+
+			this.$customer_section.find('.customer-name').html(
+				`<div class="text-md text-dark-grey text-bold">${this.customer_info.customer}</div>
+				<div class="last-transacted-on text-grey-200"></div>`
+			)
+	
+			this.$customer_section.find('.customer-details').append(
+				`<div class="customer-form">
+					<div class="text-grey mt-4 mb-6">CONTACT DETAILS</div>
+					<div class="grid grid-cols-2 gap-4">
+						<div class="email_id-field"></div>
+						<div class="mobile_no-field"></div>
+						<div class="loyalty_program-field"></div>
+						<div class="loyalty_points-field"></div>
+					</div>
+					<div class="text-grey mt-4 mb-6">RECENT TRANSACTIONS</div>
+				</div>`
+			)
+			// transactions need to be in diff div from sticky elem for scrolling
+			this.$customer_section.append(`<div class="customer-transactions flex-1 rounded"></div>`)
+
+			this.render_customer_info_form();
+			this.fetch_customer_transactions();
+
+		} else {
+			this.$cart_container.removeClass('d-none');
+			this.$customer_section.removeClass('flex-1 scroll-y').addClass('mb-0 border pr-4 pl-4');
+			this.$customer_section.find('.icon').addClass('w-12 h-12 text-md').removeClass('w-24 h-24 text-2xl');
+			this.$customer_section.find('.customer-header').addClass('h-18')
+			this.$customer_section.find('.customer-details').removeClass('sticky z-100 bg-white');
+
+			this.update_customer_section();
+		}
+	}
+
+	render_customer_info_form() {
+		const $customer_form = this.$customer_section.find('.customer-form');
+
+		const dfs = [{
+			fieldname: 'email_id',
+			label: __('Email'),
+			fieldtype: 'Data',
+			options: 'email',
+			placeholder: __("Enter customer's email")
+		},{
+			fieldname: 'mobile_no',
+			label: __('Phone Number'),
+			fieldtype: 'Data',
+			placeholder: __("Enter customer's phone number")
+		},{
+			fieldname: 'loyalty_program',
+			label: __('Loyalty Program'),
+			fieldtype: 'Link',
+			options: 'Loyalty Program',
+			placeholder: __("Select Loyalty Program")
+		},{
+			fieldname: 'loyalty_points',
+			label: __('Loyalty Points'),
+			fieldtype: 'Int',
+			read_only: 1
+		}];
+
+		const me = this;
+		dfs.forEach(df => {
+			this[`customer_${df.fieldname}_field`] = frappe.ui.form.make_control({
+				df: { ...df,
+					onchange: handle_customer_field_change,
+				},
+				parent: $customer_form.find(`.${df.fieldname}-field`),
+				render_input: true,
+			});
+			this[`customer_${df.fieldname}_field`].set_value(this.customer_info[df.fieldname]);
+		})
+
+		function handle_customer_field_change() {
+			const current_value = me.customer_info[this.df.fieldname];
+			const current_customer = me.customer_info.customer;
+
+			if (this.value && current_value != this.value && this.df.fieldname != 'loyalty_points') {
+				frappe.call({
+					method: 'erpnext.selling.page.point_of_sale.point_of_sale.set_customer_info',
+					args: {
+						fieldname: this.df.fieldname,
+						customer: current_customer,
+						value: this.value
+					},
+					callback: (r) => {
+						if(!r.exc) {
+							me.customer_info[this.df.fieldname] = this.value;
+							frappe.show_alert({
+								message: __("Customer contact updated successfully."),
+								indicator: 'green'
+							});
+							frappe.utils.play_sound("submit");
+						}
+					}
+				});
+			}
+		}
+	}
+
+	fetch_customer_transactions() {
+		frappe.db.get_list('POS Invoice', { 
+			filters: { customer: this.customer_info.customer, docstatus: 1 },
+			fields: ['name', 'grand_total', 'status', 'posting_date', 'posting_time', 'currency'],
+			limit: 20
+		}).then((res) => {
+			const transaction_container = this.$customer_section.find('.customer-transactions');
+
+			if (!res.length) {
+				transaction_container.removeClass('flex-1 border rounded').html(
+					`<div class="text-grey text-center">No recent transactions found</div>`
+				)
+				return;
+			};
+
+			const elapsed_time = moment(res[0].posting_date+" "+res[0].posting_time).fromNow();
+			this.$customer_section.find('.last-transacted-on').html(`Last transacted ${elapsed_time}`);
+
+			res.forEach(invoice => {
+				const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma");
+				let indicator_color = '';
+
+				if (in_list(['Paid', 'Consolidated'], invoice.status)) (indicator_color = 'green');
+				if (invoice.status === 'Draft') (indicator_color = 'red');
+				if (invoice.status === 'Return') (indicator_color = 'grey');
+
+				transaction_container.append(
+					`<div class="invoice-wrapper flex p-3 justify-between border-grey rounded pointer no-select" data-invoice-name="${escape(invoice.name)}">
+						<div class="flex flex-col justify-end">
+							<div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
+							<div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+								${posting_datetime}
+							</div>
+						</div>
+						<div class="flex flex-col text-right">
+							<div class="f-shrink-0 text-md text-dark-grey text-bold ml-4">
+								${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
+							</div>
+							<div class="f-shrink-0 text-grey ml-4 text-bold indicator ${indicator_color}">${invoice.status}</div>
+						</div>
+					</div>`
+				)
+			});
+		})
+	}
+
+	load_invoice() {
+		const frm = this.events.get_frm();
+		this.fetch_customer_details(frm.doc.customer).then(() => {
+			this.events.customer_details_updated(this.customer_info);
+			this.update_customer_section();
+		})
+		
+		this.$cart_items_wrapper.html('');
+		if (frm.doc.items.length) {
+			frm.doc.items.forEach(item => {
+				this.update_item_html(item);
+			});
+		} else {
+			this.make_no_items_placeholder();
+			this.highlight_checkout_btn(false);
+		}
+
+		this.update_totals_section(frm);
+
+		if(frm.doc.docstatus === 1) {
+			this.$totals_section.find('.checkout-btn').addClass('d-none');
+			this.$totals_section.find('.edit-cart-btn').addClass('d-none');
+			this.$totals_section.find('.grand-total').removeClass('border-b-grey');
+		} else {
+			this.$totals_section.find('.checkout-btn').removeClass('d-none');
+			this.$totals_section.find('.edit-cart-btn').addClass('d-none');
+			this.$totals_section.find('.grand-total').addClass('border-b-grey');
+		}
+
+		this.toggle_component(true);
+	}
+
+	toggle_component(show) {
+		show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+    }
+    
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
new file mode 100644
index 0000000..86a1be9
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -0,0 +1,394 @@
+erpnext.PointOfSale.ItemDetails = class {
+    constructor({ wrapper, events }) {
+		this.wrapper = wrapper;
+        this.events = events;
+        this.current_item = {};
+
+        this.init_component();
+    }
+
+    init_component() {
+        this.prepare_dom();
+        this.init_child_components();
+		this.bind_events();
+		this.attach_shortcuts();
+    }
+
+    prepare_dom() {
+        this.wrapper.append(
+            `<section class="col-span-4 flex shadow rounded item-details bg-white mx-h-70 h-100 d-none"></section>`
+        )
+
+        this.$component = this.wrapper.find('.item-details');
+    }
+
+    init_child_components() {
+		this.$component.html(
+			`<div class="details-container flex flex-col p-8 rounded w-full">
+				<div class="flex justify-between mb-2">
+					<div class="text-grey">ITEM DETAILS</div>
+					<div class="close-btn text-grey hover-underline pointer no-select">Close</div>
+				</div>
+				<div class="item-defaults flex">
+					<div class="flex-1 flex flex-col justify-end mr-4 mb-2">
+						<div class="item-name text-xl font-weight-450"></div>
+						<div class="item-description text-md-0 text-grey-200"></div>
+						<div class="item-price text-xl font-bold"></div>
+					</div>
+					<div class="item-image flex items-center justify-center w-46 h-46 bg-light-grey rounded ml-4 text-6xl text-grey-100"></div>
+				</div>
+				<div class="discount-section flex items-center"></div>
+				<div class="text-grey mt-4 mb-6">STOCK DETAILS</div>
+				<div class="form-container grid grid-cols-2 row-gap-2 col-gap-4 grid-auto-row"></div>
+			</div>`
+		)
+
+		this.$item_name = this.$component.find('.item-name');
+		this.$item_description = this.$component.find('.item-description');
+		this.$item_price = this.$component.find('.item-price');
+		this.$item_image = this.$component.find('.item-image');
+		this.$form_container = this.$component.find('.form-container');
+		this.$dicount_section = this.$component.find('.discount-section');
+    }
+
+    toggle_item_details_section(item) {
+		const { item_code, batch_no, uom } = this.current_item; 
+		const item_code_is_same = item && item_code === item.item_code;
+		const batch_is_same = item && batch_no == item.batch_no;
+		const uom_is_same = item && uom === item.uom;
+
+        this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true;
+
+        this.events.toggle_item_selector(this.item_has_changed);
+		this.toggle_component(this.item_has_changed);
+        
+		if (this.item_has_changed) {
+            this.doctype = item.doctype;
+			this.item_meta = frappe.get_meta(this.doctype);
+			this.name = item.name;
+			this.item_row = item;
+            this.currency = this.events.get_frm().doc.currency;
+            
+            this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom };
+            
+			this.render_dom(item);
+			this.render_discount_dom(item);
+			this.render_form(item);
+		} else {
+			this.validate_serial_batch_item();
+			this.current_item = {};
+		}
+	}
+	
+	validate_serial_batch_item() {
+		const doc = this.events.get_frm().doc;
+		const item_row = doc.items.find(item => item.name === this.name);
+
+		if (!item_row) return;
+
+		const serialized = item_row.has_serial_no;
+		const batched = item_row.has_batch_no;
+		const no_serial_selected = !item_row.serial_no;
+		const no_batch_selected = !item_row.batch_no;
+
+		if ((serialized && no_serial_selected) || (batched && no_batch_selected) || 
+			(serialized && batched && (no_batch_selected || no_serial_selected))) {
+
+			frappe.show_alert({
+				message: __("Item will be removed since no serial / batch no selected."),
+				indicator: 'orange'
+			});
+			frappe.utils.play_sound("cancel");
+			this.events.remove_item_from_cart();
+		}
+	}
+    
+    render_dom(item) {
+        let { item_code ,item_name, description, image, price_list_rate } = item;
+
+		function get_description_html() {
+			if (description) {
+				description = description.indexOf('...') === -1 && description.length > 75 ? description.substr(0, 73) + '...' : description;
+				return description;
+			}
+			return ``;
+        }
+        
+		this.$item_name.html(item_name);
+		this.$item_description.html(get_description_html());
+		this.$item_price.html(format_currency(price_list_rate, this.currency));
+		if (image) {
+			this.$item_image.html(
+				`<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">`
+			);
+		} else {
+			this.$item_image.html(frappe.get_abbr(item_code));
+		}
+
+    }
+    
+    render_discount_dom(item) {
+		if (item.discount_percentage) {
+			this.$dicount_section.html(
+				`<div class="text-grey line-through mr-4 text-md mb-2">
+					${format_currency(item.price_list_rate, this.currency)}
+				</div>
+				<div class="p-1 pr-3 pl-3 rounded w-fit text-bold bg-green-200 mb-2">
+					${item.discount_percentage}% off
+				</div>`
+			)
+			this.$item_price.html(format_currency(item.rate, this.currency));
+		} else {
+			this.$dicount_section.html(``)
+		}
+    }
+
+    render_form(item) {
+		const fields_to_display = this.get_form_fields(item);
+		this.$form_container.html('');
+
+		fields_to_display.forEach((fieldname, idx) => {
+			this.$form_container.append(
+				`<div class="">
+					<div class="item_detail_field ${fieldname}-control" data-fieldname="${fieldname}"></div>
+				</div>`
+			)
+
+			const field_meta = this.item_meta.fields.find(df => df.fieldname === fieldname);
+			fieldname === 'discount_percentage' ? (field_meta.label = __('Discount (%)')) : '';
+			const me = this;
+            
+			this[`${fieldname}_control`] = frappe.ui.form.make_control({
+				df: { 
+					...field_meta, 
+					onchange: function() {
+						me.events.form_updated(me.doctype, me.name, fieldname, this.value);
+					}
+				},
+				parent: this.$form_container.find(`.${fieldname}-control`),
+				render_input: true,
+			})
+			this[`${fieldname}_control`].set_value(item[fieldname]);
+		});
+
+		this.make_auto_serial_selection_btn(item);
+
+		this.bind_custom_control_change_event();
+    }
+
+    get_form_fields(item) {
+		const fields = ['qty', 'uom', 'rate', 'price_list_rate', 'discount_percentage', 'warehouse', 'actual_qty'];
+		if (item.has_serial_no) fields.push('serial_no');
+		if (item.has_batch_no) fields.push('batch_no');
+		return fields;
+	}
+
+    make_auto_serial_selection_btn(item) {
+		if (item.has_serial_no) {
+			this.$form_container.append(
+				`<div class="grid-filler no-select"></div>`
+			)
+			if (!item.has_batch_no) {
+				this.$form_container.append(
+					`<div class="grid-filler no-select"></div>`
+				)	
+			}
+			this.$form_container.append(
+				`<div class="auto-fetch-btn bg-grey-100 border border-grey text-bold rounded pt-3 pb-3 pl-6 pr-8 text-grey pointer no-select mt-2"
+						style="height: 3.3rem">
+					Auto Fetch Serial Numbers
+				</div>`
+			)
+			this.$form_container.find('.serial_no-control').find('textarea').css('height', '9rem');
+			this.$form_container.find('.serial_no-control').parent().addClass('row-span-2');
+		}
+	}
+    
+    bind_custom_control_change_event() {
+		const me = this;
+		if (this.rate_control) {
+			this.rate_control.df.onchange = function() {
+				if (this.value) {
+					me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
+						const item_row = frappe.get_doc(me.doctype, me.name);
+						const doc = me.events.get_frm().doc;
+
+						me.$item_price.html(format_currency(item_row.rate, doc.currency));
+						me.render_discount_dom(item_row);
+					});
+				}
+			}
+		}
+
+		if (this.warehouse_control) {
+			this.warehouse_control.df.reqd = 1;
+			this.warehouse_control.df.onchange = function() {
+				if (this.value) {
+					me.events.form_updated(me.doctype, me.name, 'warehouse', this.value).then(() => {
+						me.item_stock_map = me.events.get_item_stock_map();
+						const available_qty = me.item_stock_map[me.item_row.item_code][this.value];
+						if (available_qty === undefined) {
+							me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
+								// item stock map is updated now reset warehouse
+								me.warehouse_control.set_value(this.value);
+							})
+						} else if (available_qty === 0) {
+							me.warehouse_control.set_value('');
+							frappe.throw(__(`Item Code: ${me.item_row.item_code.bold()} is not available under warehouse ${this.value.bold()}.`));
+						}
+						me.actual_qty_control.set_value(available_qty);
+					});
+				}
+			}
+			this.warehouse_control.refresh();
+		}
+
+		if (this.discount_percentage_control) {
+			this.discount_percentage_control.df.onchange = function() {
+				if (this.value) {
+					me.events.form_updated(me.doctype, me.name, 'discount_percentage', this.value).then(() => {
+						const item_row = frappe.get_doc(me.doctype, me.name);
+						me.rate_control.set_value(item_row.rate);
+					});
+				}
+			}
+		}
+
+		if (this.serial_no_control) {
+			this.serial_no_control.df.reqd = 1;
+			this.serial_no_control.df.onchange = async function() {
+				!me.current_item.batch_no && await me.auto_update_batch_no();
+				me.events.form_updated(me.doctype, me.name, 'serial_no', this.value);
+			}
+			this.serial_no_control.refresh();
+		}
+
+		if (this.batch_no_control) {
+			this.batch_no_control.df.reqd = 1;
+			this.batch_no_control.df.get_query = () => {
+				return {
+					query: 'erpnext.controllers.queries.get_batch_no',
+					filters: {
+						item_code: me.item_row.item_code,
+						warehouse: me.item_row.warehouse
+					}
+				}
+			};
+			this.batch_no_control.df.onchange = function() {
+				me.events.set_value_in_current_cart_item('batch-no', this.value);
+                me.events.form_updated(me.doctype, me.name, 'batch_no', this.value);
+                me.current_item.batch_no = this.value;
+			}
+			this.batch_no_control.refresh();
+		}
+
+		if (this.uom_control) {
+			this.uom_control.df.onchange = function() {
+				me.events.set_value_in_current_cart_item('uom', this.value);
+				me.events.form_updated(me.doctype, me.name, 'uom', this.value);
+				me.current_item.uom = this.value;
+			}
+		}
+    }
+    
+    async auto_update_batch_no() {
+		if (this.serial_no_control && this.batch_no_control) {
+			const selected_serial_nos = this.serial_no_control.get_value().split(`\n`).filter(s => s);
+			if (!selected_serial_nos.length) return;
+
+			// find batch nos of the selected serial no 
+			const serials_with_batch_no = await frappe.db.get_list("Serial No", {
+				filters: { 'name': ["in", selected_serial_nos]},
+				fields: ["batch_no", "name"]
+			});
+			const batch_serial_map = serials_with_batch_no.reduce((acc, r) => {
+				acc[r.batch_no] || (acc[r.batch_no] = []);
+				acc[r.batch_no] = [...acc[r.batch_no], r.name];
+				return acc;
+			}, {});
+			// set current item's batch no and serial no
+			const batch_no = Object.keys(batch_serial_map)[0];
+			const batch_serial_nos = batch_serial_map[batch_no].join(`\n`);
+			// eg. 10 selected serial no. -> 5 belongs to first batch other 5 belongs to second batch
+            const serial_nos_belongs_to_other_batch = selected_serial_nos.length !== batch_serial_map[batch_no].length;
+            
+            const current_batch_no = this.batch_no_control.get_value();
+			current_batch_no != batch_no && await this.batch_no_control.set_value(batch_no);
+
+			if (serial_nos_belongs_to_other_batch) {
+				this.serial_no_control.set_value(batch_serial_nos);
+				this.qty_control.set_value(batch_serial_map[batch_no].length);
+			}
+
+			delete batch_serial_map[batch_no];
+
+			if (serial_nos_belongs_to_other_batch)
+				this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item);
+		}
+	}
+    
+    bind_events() {
+		this.bind_auto_serial_fetch_event();
+		this.bind_fields_to_numpad_fields();
+
+		this.$component.on('click', '.close-btn', () => {
+			this.events.close_item_details();
+		});
+	}
+
+	attach_shortcuts() {
+		frappe.ui.keys.on("escape", () => {
+			const item_details_visible = this.$component.is(":visible");
+			if (item_details_visible) {
+				this.events.close_item_details();
+			}
+		});
+	}
+
+    bind_fields_to_numpad_fields() {
+		const me = this;
+		this.$form_container.on('click', '.input-with-feedback', function() {
+			const fieldname = $(this).attr('data-fieldname');
+			if (this.last_field_focused != fieldname) {
+				me.events.item_field_focused(fieldname);
+				this.last_field_focused = fieldname;
+			}
+		});
+	}
+    
+    bind_auto_serial_fetch_event() {
+		this.$form_container.on('click', '.auto-fetch-btn', () => {
+			this.batch_no_control.set_value('');
+			let qty = this.qty_control.get_value();
+			let numbers = frappe.call({
+				method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number",
+				args: {
+					qty,
+					item_code: this.current_item.item_code,
+					warehouse: this.warehouse_control.get_value() || '',
+					batch_nos: this.current_item.batch_no || '',
+					for_doctype: 'POS Invoice'
+				}
+			});
+
+			numbers.then((data) => {
+				let auto_fetched_serial_numbers = data.message;
+				let records_length = auto_fetched_serial_numbers.length;
+				if (!records_length) {
+					const warehouse = this.warehouse_control.get_value().bold();
+					frappe.msgprint(__(`Serial numbers unavailable for Item ${this.current_item.item_code.bold()} 
+						under warehouse ${warehouse}. Please try changing warehouse.`));
+				} else if (records_length < qty) {
+					frappe.msgprint(`Fetched only ${records_length} available serial numbers.`);
+					this.qty_control.set_value(records_length);
+				}
+                numbers = auto_fetched_serial_numbers.join(`\n`);
+				this.serial_no_control.set_value(numbers);
+			});
+		})
+	}
+
+	toggle_component(show) {
+		show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+    }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
new file mode 100644
index 0000000..ee0c06d
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -0,0 +1,265 @@
+erpnext.PointOfSale.ItemSelector = class {
+    constructor({ frm, wrapper, events, pos_profile }) {
+		this.wrapper = wrapper;
+		this.events = events;
+        this.pos_profile = pos_profile;
+        
+        this.inti_component();
+    }
+    
+    inti_component() {
+        this.prepare_dom();
+        this.make_search_bar();
+        this.load_items_data();
+        this.bind_events();
+        this.attach_shortcuts();
+    }
+
+    prepare_dom() {
+		this.wrapper.append(
+            `<section class="col-span-6 flex shadow rounded items-selector bg-white mx-h-70 h-100">
+                <div class="flex flex-col rounded w-full scroll-y">
+                    <div class="filter-section flex p-8 pb-2 bg-white sticky z-100">
+                        <div class="search-field flex f-grow-3 mr-8 items-center text-grey"></div>
+                        <div class="item-group-field flex f-grow-1 items-center text-grey text-bold"></div>
+                    </div>
+                    <div class="flex flex-1 flex-col p-8 pt-2">
+                        <div class="text-grey mb-6">ALL ITEMS</div>
+                        <div class="items-container grid grid-cols-4 gap-8">
+                        </div>					
+                    </div>
+                </div>
+            </section>`
+        );
+        
+        this.$component = this.wrapper.find('.items-selector');
+    }
+
+    async load_items_data() {
+        if (!this.item_group) {
+            const res = await frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name");
+            this.parent_item_group = res.message.name;
+        };
+        if (!this.price_list) {
+            const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list");
+            this.price_list = res.message.selling_price_list;
+        }
+
+        this.get_items({}).then(({message}) => {
+            this.render_item_list(message.items);
+        });
+    }
+
+    get_items({start = 0, page_length = 40, search_value=''}) {
+        const price_list = this.events.get_frm().doc?.selling_price_list || this.price_list;
+        let { item_group, pos_profile } = this;
+
+        !item_group && (item_group = this.parent_item_group);
+        
+		return frappe.call({
+			method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
+			freeze: true,
+            args: { start, page_length, price_list, item_group, search_value, pos_profile },
+        });
+	}
+
+
+	render_item_list(items) {
+        this.$items_container = this.$component.find('.items-container');
+        this.$items_container.html('');
+
+        items.forEach(item => {
+            const item_html = this.get_item_html(item);
+            this.$items_container.append(item_html);
+        })
+    }
+
+    get_item_html(item) {
+        const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
+        const indicator_color = actual_qty > 10 ? "green" : actual_qty !== 0 ? "orange" : "red";
+
+        function get_item_image_html() {
+            if (item_image) {
+                return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+                            <img class="h-full" src="${item_image}" alt="${item_image}" style="object-fit: cover;">
+                        </div>`
+            } else {
+                return `<div class="flex items-center justify-center h-32 bg-light-grey text-6xl text-grey-100">
+                            ${frappe.get_abbr(item.item_name)}
+                        </div>`
+            }
+        }
+
+		return (
+            `<div class="item-wrapper rounded shadow pointer no-select" data-item-code="${escape(item.item_code)}"
+                data-serial-no="${escape(serial_no)}" data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
+                title="Avaiable Qty: ${actual_qty}">
+                ${get_item_image_html()}
+                <div class="flex items-center pr-4 pl-4 h-10 justify-between">
+                    <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+                        <span class="indicator ${indicator_color}"></span>
+                        ${frappe.ellipsis(item.item_name, 18)}
+                    </div>
+                    <div class="f-shrink-0 text-dark-grey text-bold ml-4">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
+                </div>
+            </div>`
+        )
+    }
+
+    make_search_bar() {
+        const me = this;
+        this.$component.find('.search-field').html('');
+        this.$component.find('.item-group-field').html('');
+
+		this.search_field = frappe.ui.form.make_control({
+			df: {
+				label: __('Search'),
+				fieldtype: 'Data',
+				placeholder: __('Search by item code, serial number, batch no or barcode')
+			},
+			parent: this.$component.find('.search-field'),
+			render_input: true,
+        });
+		this.item_group_field = frappe.ui.form.make_control({
+			df: {
+				label: __('Item Group'),
+				fieldtype: 'Link',
+				options: 'Item Group',
+                placeholder: __('Select item group'),
+                onchange: function() {
+                    me.item_group = this.value;
+                    !me.item_group && (me.item_group = me.parent_item_group);
+                    me.filter_items();
+                },
+                get_query: function () {
+                    return {
+                        query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query',
+                        filters: {
+                            pos_profile: me.events.get_frm().doc?.pos_profile
+                        }
+                    }
+                },
+			},
+            parent: this.$component.find('.item-group-field'),
+			render_input: true,
+        });
+        this.search_field.toggle_label(false);
+		this.item_group_field.toggle_label(false);
+	}
+
+    bind_events() {
+        const me = this;
+        onScan.attachTo(document, {
+            onScan: (sScancode) => {
+                if (this.search_field && this.$component.is(':visible')) {
+                    this.search_field.set_focus();
+                    $(this.search_field.$input[0]).val(sScancode).trigger("input");
+                    this.barcode_scanned = true;
+                }
+            }
+        });
+
+		this.$component.on('click', '.item-wrapper', function() {
+			const $item = $(this);
+			const item_code = unescape($item.attr('data-item-code'));
+            let batch_no = unescape($item.attr('data-batch-no'));
+            let serial_no = unescape($item.attr('data-serial-no'));
+            let uom = unescape($item.attr('data-uom'));
+            
+            // escape(undefined) returns "undefined" then unescape returns "undefined"
+            batch_no = batch_no === "undefined" ? undefined : batch_no;
+            serial_no = serial_no === "undefined" ? undefined : serial_no;
+            uom = uom === "undefined" ? undefined : uom;
+
+            me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }});
+        })
+
+        this.search_field.$input.on('input', (e) => {
+			clearTimeout(this.last_search);
+			this.last_search = setTimeout(() => {
+				const search_term = e.target.value;
+				this.filter_items({ search_term });
+			}, 300);
+        });
+    }
+
+    attach_shortcuts() {
+        frappe.ui.keys.on("ctrl+i", () => {
+            const selector_is_visible = this.$component.is(':visible');
+            if (!selector_is_visible) return;
+            this.search_field.set_focus();
+        });
+        frappe.ui.keys.on("ctrl+g", () => {
+            const selector_is_visible = this.$component.is(':visible');
+            if (!selector_is_visible) return;
+            this.item_group_field.set_focus();
+        });
+        // for selecting the last filtered item on search
+        frappe.ui.keys.on("enter", () => {
+            const selector_is_visible = this.$component.is(':visible');
+            if (!selector_is_visible || this.search_field.get_value() === "") return;
+
+            if (this.items.length == 1) {
+                this.$items_container.find(".item-wrapper").click();
+                frappe.utils.play_sound("submit");
+                $(this.search_field.$input[0]).val("").trigger("input");
+            } else if (this.items.length == 0 && this.barcode_scanned) {
+                // only show alert of barcode is scanned and enter is pressed
+                frappe.show_alert({
+                    message: __("No items found. Scan barcode again."),
+                    indicator: 'orange'
+                });
+                frappe.utils.play_sound("error");
+                this.barcode_scanned = false;
+                $(this.search_field.$input[0]).val("").trigger("input");
+            }
+        });
+    }
+    
+    filter_items({ search_term='' }={}) {
+		if (search_term) {
+			search_term = search_term.toLowerCase();
+
+			// memoize
+			this.search_index = this.search_index || {};
+			if (this.search_index[search_term]) {
+				const items = this.search_index[search_term];
+				this.items = items;
+				this.render_item_list(items);
+				return;
+            }
+		}
+
+		this.get_items({ search_value: search_term })
+            .then(({ message }) => {
+                const { items, serial_no, batch_no, barcode } = message;
+				if (search_term && !barcode) {
+					this.search_index[search_term] = items;
+				}
+				this.items = items;
+                this.render_item_list(items);
+            });
+	}
+    
+    resize_selector(minimize) {
+        minimize ? 
+        this.$component.find('.search-field').removeClass('mr-8') : 
+        this.$component.find('.search-field').addClass('mr-8');
+
+        minimize ? 
+        this.$component.find('.filter-section').addClass('flex-col') : 
+        this.$component.find('.filter-section').removeClass('flex-col');
+
+        minimize ?
+        this.$component.removeClass('col-span-6').addClass('col-span-2') :
+        this.$component.removeClass('col-span-2').addClass('col-span-6')
+
+        minimize ?
+        this.$items_container.removeClass('grid-cols-4').addClass('grid-cols-1') :
+        this.$items_container.removeClass('grid-cols-1').addClass('grid-cols-4')
+    }
+
+    toggle_component(show) {
+		show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+    }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_number_pad.js b/erpnext/selling/page/point_of_sale/pos_number_pad.js
new file mode 100644
index 0000000..2ffc2c0
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_number_pad.js
@@ -0,0 +1,49 @@
+erpnext.PointOfSale.NumberPad = class {
+    constructor({ wrapper, events, cols, keys, css_classes, fieldnames_map }) {
+        this.wrapper = wrapper;
+        this.events = events;
+        this.cols = cols;
+        this.keys = keys;
+        this.css_classes = css_classes || [];
+        this.fieldnames = fieldnames_map || {};
+
+        this.init_component();
+    }
+
+    init_component() {
+        this.prepare_dom();
+        this.bind_events();
+    }
+
+    prepare_dom() {
+        const { cols, keys, css_classes, fieldnames } = this;
+
+        function get_keys() {
+           return keys.reduce((a, row, i) => {
+               return a + row.reduce((a2, number, j) => {
+                   const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : '';
+                   const fieldname = fieldnames && fieldnames[number] ? 
+                       fieldnames[number] : 
+                       typeof number === 'string' ? frappe.scrub(number) : number;
+                   
+                   return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append}
+                                       flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>`
+               }, '')
+           }, '');
+       }
+
+        this.wrapper.html(
+            `<div class="grid grid-cols-${cols} gap-4">
+                ${get_keys()}
+           </div>`
+        )
+    }
+
+    bind_events() {
+       const me = this;
+       this.wrapper.on('click', '.numpad-btn', function() {
+           const $btn = $(this);
+           me.events.numpad_event($btn);
+       })
+    }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
new file mode 100644
index 0000000..9181ee8
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
@@ -0,0 +1,130 @@
+erpnext.PointOfSale.PastOrderList = class {
+    constructor({ wrapper, events }) {
+        this.wrapper = wrapper;
+        this.events = events;
+
+        this.init_component();
+    }
+
+    init_component() {
+        this.prepare_dom();
+        this.make_filter_section();
+        this.bind_events();
+    }
+
+    prepare_dom() {
+        this.wrapper.append(
+            `<section class="col-span-4 flex flex-col shadow rounded past-order-list bg-white mx-h-70 h-100 d-none">
+                <div class="flex flex-col rounded w-full scroll-y">
+                    <div class="filter-section flex flex-col p-8 pb-2 bg-white sticky z-100">
+                        <div class="search-field flex items-center text-grey"></div>
+                        <div class="status-field flex items-center text-grey text-bold"></div>
+                    </div>
+                    <div class="flex flex-1 flex-col p-8 pt-2">
+                        <div class="text-grey mb-6">RECENT ORDERS</div>
+                        <div class="invoices-container rounded border grid grid-cols-1"></div>					
+                    </div>
+                </div>
+            </section>`
+        )
+
+        this.$component = this.wrapper.find('.past-order-list');
+        this.$invoices_container = this.$component.find('.invoices-container');
+    }
+
+    bind_events() {
+        this.search_field.$input.on('input', (e) => {
+			clearTimeout(this.last_search);
+			this.last_search = setTimeout(() => {
+                const search_term = e.target.value;
+                this.refresh_list(search_term, this.status_field.get_value());
+			}, 300);
+        });
+        const me = this;
+        this.$invoices_container.on('click', '.invoice-wrapper', function() {
+            const invoice_name = unescape($(this).attr('data-invoice-name'));
+
+            me.events.open_invoice_data(invoice_name);
+        })
+    }
+
+    make_filter_section() {
+        const me = this;
+		this.search_field = frappe.ui.form.make_control({
+			df: {
+				label: __('Search'),
+				fieldtype: 'Data',
+				placeholder: __('Search by invoice id or customer name')
+			},
+			parent: this.$component.find('.search-field'),
+			render_input: true,
+        });
+		this.status_field = frappe.ui.form.make_control({
+			df: {
+				label: __('Invoice Status'),
+                fieldtype: 'Select',
+				options: `Draft\nPaid\nConsolidated\nReturn`,
+                placeholder: __('Filter by invoice status'),
+                onchange: function() {
+                    me.refresh_list(me.search_field.get_value(), this.value);
+                }
+			},
+            parent: this.$component.find('.status-field'),
+			render_input: true,
+        });
+        this.search_field.toggle_label(false);
+        this.status_field.toggle_label(false);
+        this.status_field.set_value('Paid');
+    }
+    
+    toggle_component(show) {
+        show ? 
+        this.$component.removeClass('d-none') && this.refresh_list() :
+        this.$component.addClass('d-none');
+    }
+
+    refresh_list() {
+        frappe.dom.freeze();
+        this.events.reset_summary();
+        const search_term = this.search_field.get_value();
+        const status = this.status_field.get_value();
+
+        this.$invoices_container.html('');
+
+        return frappe.call({
+			method: "erpnext.selling.page.point_of_sale.point_of_sale.get_past_order_list",
+			freeze: true,
+            args: { search_term, status },
+            callback: (response) => {
+                frappe.dom.unfreeze();
+                response.message.forEach(invoice => {
+                    const invoice_html = this.get_invoice_html(invoice);
+                    this.$invoices_container.append(invoice_html);
+                });
+            }
+       });
+    }
+
+    get_invoice_html(invoice) {
+        const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma");
+        return (
+            `<div class="invoice-wrapper flex p-4 justify-between border-b-grey pointer no-select" data-invoice-name="${escape(invoice.name)}">
+                <div class="flex flex-col justify-end">
+                    <div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
+                    <div class="flex items-center">
+                        <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+                            <svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
+                                <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
+                            </svg>
+                            ${invoice.customer}
+                        </div>
+                    </div>
+                </div>
+                <div class="flex flex-col text-right">
+                    <div class="f-shrink-0 text-lg text-dark-grey text-bold ml-4">${format_currency(invoice.grand_total, invoice.currency, 0) || 0}</div>
+                    <div class="f-shrink-0 text-grey ml-4">${posting_datetime}</div>
+                </div>
+            </div>`
+        )
+    }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
new file mode 100644
index 0000000..24326b2
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -0,0 +1,452 @@
+erpnext.PointOfSale.PastOrderSummary = class {
+    constructor({ wrapper, events }) {
+        this.wrapper = wrapper;
+        this.events = events;
+
+        this.init_component();
+    }
+
+    init_component() {
+        this.prepare_dom();
+        this.init_child_components();
+        this.bind_events();
+        this.attach_shortcuts();
+    }
+
+    prepare_dom() {
+        this.wrapper.append(
+            `<section class="col-span-6 flex flex-col items-center shadow rounded past-order-summary bg-white mx-h-70 h-100 d-none">
+                <div class="no-summary-placeholder flex flex-1 items-center justify-center p-16">
+                    <div class="no-item-wrapper flex items-center h-18 pr-4 pl-4">
+                        <div class="flex-1 text-center text-grey">Select an invoice to load summary data</div>
+                    </div>
+                </div>
+                <div class="summary-wrapper d-none flex-1 w-66 text-dark-grey relative">
+                    <div class="summary-container absolute flex flex-col pt-16 pb-16 pr-8 pl-8 w-full h-full"></div>
+                </div>
+            </section>`
+        )
+
+        this.$component = this.wrapper.find('.past-order-summary');
+        this.$summary_wrapper = this.$component.find('.summary-wrapper');
+        this.$summary_container = this.$component.find('.summary-container');
+    }
+
+    init_child_components() {
+        this.init_upper_section();
+        this.init_items_summary();
+        this.init_totals_summary();
+        this.init_payments_summary();
+        this.init_summary_buttons();
+        this.init_email_print_dialog();
+    }
+
+    init_upper_section() {
+        this.$summary_container.append(
+            `<div class="flex upper-section justify-between w-full h-24"></div>`
+        );
+
+        this.$upper_section = this.$summary_container.find('.upper-section');
+    }
+
+    init_items_summary() {
+        this.$summary_container.append(
+            `<div class="flex flex-col flex-1 mt-6 w-full scroll-y">
+                <div class="text-grey mb-4 sticky bg-white">ITEMS</div>
+                <div class="items-summary-container border rounded flex flex-col w-full"></div>
+            </div>`
+        )
+
+        this.$items_summary_container = this.$summary_container.find('.items-summary-container');
+    }
+
+    init_totals_summary() {
+        this.$summary_container.append(
+            `<div class="flex flex-col mt-6 w-full f-shrink-0">
+                <div class="text-grey mb-4">TOTALS</div>
+                <div class="summary-totals-container border rounded flex flex-col w-full"></div>
+            </div>`
+        )
+
+        this.$totals_summary_container = this.$summary_container.find('.summary-totals-container');
+    }
+
+    init_payments_summary() {
+        this.$summary_container.append(
+            `<div class="flex flex-col mt-6 w-full f-shrink-0">
+                <div class="text-grey mb-4">PAYMENTS</div>
+                <div class="payments-summary-container border rounded flex flex-col w-full mb-4"></div>
+            </div>`
+        )
+
+        this.$payment_summary_container = this.$summary_container.find('.payments-summary-container');
+    }
+
+    init_summary_buttons() {
+        this.$summary_container.append(
+            `<div class="summary-btns flex summary-btns justify-between w-full f-shrink-0"></div>`
+        )
+        
+        this.$summary_btns = this.$summary_container.find('.summary-btns');
+    }
+
+    init_email_print_dialog() {
+        const email_dialog = new frappe.ui.Dialog({
+            title: 'Email Receipt',
+            fields: [
+                {fieldname:'email_id', fieldtype:'Data', options: 'Email', label:'Email ID'},
+                // {fieldname:'remarks', fieldtype:'Text', label:'Remarks (if any)'}
+            ],
+            primary_action: () => {
+                this.send_email();
+            },
+            primary_action_label: __('Send'),
+        });
+        this.email_dialog = email_dialog;
+
+        const print_dialog = new frappe.ui.Dialog({
+            title: 'Print Receipt',
+            fields: [
+                {fieldname:'print', fieldtype:'Data', label:'Print Preview'}
+            ],
+            primary_action: () => {
+                this.events.get_frm().print_preview.printit(true);
+            },
+            primary_action_label: __('Print'),
+        });
+        this.print_dialog = print_dialog;
+    }
+
+    get_upper_section_html(doc) {
+        const { status } = doc; let indicator_color = '';
+
+        in_list(['Paid', 'Consolidated'], status) && (indicator_color = 'green');
+        status === 'Draft' && (indicator_color = 'red');
+        status === 'Return' && (indicator_color = 'grey');
+
+        return `<div class="flex flex-col items-start justify-end pr-4">
+                    <div class="text-lg text-bold pt-2">${doc.customer}</div>
+                    <div class="text-grey">${this.customer_email}</div>
+                    <div class="text-grey mt-auto">Sold by: ${doc.owner}</div>
+                </div>
+                <div class="flex flex-col flex-1 items-end justify-between">
+                    <div class="text-2-5xl text-bold">${format_currency(doc.paid_amount, doc.currency)}</div>
+                    <div class="flex justify-between">
+                        <div class="text-grey mr-4">${doc.name}</div>
+                        <div class="text-grey text-bold indicator ${indicator_color}">${doc.status}</div>
+                    </div>
+                </div>`
+    }
+
+    get_discount_html(doc) {
+        if (doc.discount_amount) {
+            return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+                    <div class="flex f-shrink-1 items-center">
+                        <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap  mr-2">
+                            Discount
+                        </div>
+                        <span class="text-grey">(${doc.additional_discount_percentage} %)</span>
+                    </div>
+                    <div class="flex flex-col f-shrink-0 ml-auto text-right">
+                        <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.discount_amount, doc.currency)}</div>
+                    </div>
+                </div>`;
+        } else {
+            return ``;
+        }
+    }
+
+    get_net_total_html(doc) {
+        return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+                    <div class="flex f-shrink-1 items-center">
+                        <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+                            Net Total
+                        </div>
+                    </div>
+                    <div class="flex flex-col f-shrink-0 ml-auto text-right">
+                        <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.net_total, doc.currency)}</div>
+                    </div>
+                </div>`
+    }
+
+    get_taxes_html(doc) {
+        return `<div class="total-summary-wrapper flex items-center justify-between h-12 pr-4 pl-4 border-b-grey">
+                    <div class="flex">
+                        <div class="text-md-0 text-dark-grey text-bold w-fit">Tax Charges</div>
+                        <div class="flex ml-6 text-dark-grey">
+                        ${	
+                            doc.taxes.map((t, i) => {
+                                let margin_left = '';
+                                if (i !== 0) margin_left = 'ml-2';
+                                return `<span class="pl-2 pr-2 ${margin_left}">${t.description} @${t.rate}%</span>`
+                            }).join('')
+                        }
+                        </div>
+                    </div>
+                    <div class="flex flex-col text-right">
+                        <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.base_total_taxes_and_charges, doc.currency)}</div>
+                    </div>
+                </div>`
+    }
+
+    get_grand_total_html(doc) {
+        return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+                    <div class="flex f-shrink-1 items-center">
+                        <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+                            Grand Total
+                        </div>
+                    </div>
+                    <div class="flex flex-col f-shrink-0 ml-auto text-right">
+                        <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.grand_total, doc.currency)}</div>
+                    </div>
+                </div>`
+    }
+
+    get_item_html(doc, item_data) {
+        return `<div class="item-summary-wrapper flex items-center h-12 pr-4 pl-4 border-b-grey pointer no-select">
+                    <div class="flex w-6 h-6 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
+                        <span>${item_data.qty || 0}</span>
+                    </div>
+                    <div class="flex flex-col f-shrink-1">
+                        <div class="text-md text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+                            ${item_data.item_name}
+                        </div>
+                    </div>
+                    <div class="flex f-shrink-0 ml-auto text-right">
+                        ${get_rate_discount_html()}
+                    </div>
+                </div>`
+
+        function get_rate_discount_html() {
+            if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
+                return `<span class="text-grey mr-2">(${item_data.discount_percentage}% off)</span>
+                        <div class="text-md-0 text-dark-grey text-bold">${format_currency(item_data.rate, doc.currency)}</div>`
+            } else {
+                return `<div class="text-md-0 text-dark-grey text-bold">${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}</div>`
+            }
+        }
+    }
+
+    get_payment_html(doc, payment) {
+        return `<div class="payment-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+                    <div class="flex f-shrink-1 items-center">
+                        <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+                            ${payment.mode_of_payment}
+                        </div>
+                    </div>
+                    <div class="flex flex-col f-shrink-0 ml-auto text-right">
+                        <div class="text-md-0 text-dark-grey text-bold">${format_currency(payment.amount, doc.currency)}</div>
+                    </div>
+                </div>`
+    }
+
+    bind_events() {
+        this.$summary_container.on('click', '.return-btn', () => {
+            this.events.process_return(this.doc.name);
+            this.toggle_component(false);
+            this.$component.find('.no-summary-placeholder').removeClass('d-none');
+            this.$summary_wrapper.addClass('d-none');
+        });
+
+        this.$summary_container.on('click', '.edit-btn', () => {
+            this.events.edit_order(this.doc.name);
+            this.toggle_component(false);
+            this.$component.find('.no-summary-placeholder').removeClass('d-none');
+            this.$summary_wrapper.addClass('d-none');
+        });
+
+        this.$summary_container.on('click', '.new-btn', () => {
+            this.events.new_order();
+            this.toggle_component(false);
+            this.$component.find('.no-summary-placeholder').removeClass('d-none');
+            this.$summary_wrapper.addClass('d-none');
+        });
+
+        this.$summary_container.on('click', '.email-btn', () => {
+            this.email_dialog.fields_dict.email_id.set_value(this.customer_email);
+            this.email_dialog.show();
+        });
+
+        this.$summary_container.on('click', '.print-btn', () => {
+            // this.print_dialog.show();
+            const frm = this.events.get_frm();
+            frm.doc = this.doc;
+            frm.print_preview.printit(true);
+        });
+    }
+
+    attach_shortcuts() {
+        frappe.ui.keys.on("ctrl+p", () => {
+            const print_btn_visible = this.$summary_container.find('.print-btn').is(":visible");
+            const summary_visible = this.$component.is(":visible");
+            if (!summary_visible || !print_btn_visible) return;
+
+            this.$summary_container.find('.print-btn').click();
+        });
+    }
+    
+    toggle_component(show) {
+        show ? 
+        this.$component.removeClass('d-none') :
+        this.$component.addClass('d-none');
+    }
+
+    send_email() {
+        const frm = this.events.get_frm();
+        const recipients = this.email_dialog.get_values().recipients;
+        const doc = this.doc || frm.doc;
+        const print_format = frm.pos_print_format;
+
+        frappe.call({
+            method:"frappe.core.doctype.communication.email.make",
+            args: {
+                recipients: recipients,
+                subject: __(frm.meta.name) + ': ' + doc.name,
+                doctype: doc.doctype,
+                name: doc.name,
+                send_email: 1,
+                print_format,
+                sender_full_name: frappe.user.full_name(),
+                _lang : doc.language
+            },
+            callback: r => {
+                if(!r.exc) {
+                    frappe.utils.play_sound("email");
+                    if(r.message["emails_not_sent_to"]) {
+                        frappe.msgprint(__("Email not sent to {0} (unsubscribed / disabled)",
+                            [ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) );
+                    } else {
+                        frappe.show_alert({
+                            message: __('Email sent successfully.'),
+                            indicator: 'green'
+                        });
+                    }
+                    this.email_dialog.hide();
+                } else {
+                    frappe.msgprint(__("There were errors while sending email. Please try again."));
+                }
+            }
+        });
+    }
+
+    add_summary_btns(map) {
+        this.$summary_btns.html('');
+        map.forEach(m => {
+            if (m.condition) {
+                m.visible_btns.forEach(b => {
+                    const class_name = b.split(' ')[0].toLowerCase();
+                    this.$summary_btns.append(
+                        `<div class="${class_name}-btn border rounded h-14 flex flex-1 items-center mr-4 justify-center text-md text-bold no-select pointer">
+                            ${b}
+                        </div>`
+                    )
+                });
+            }
+        });
+        this.$summary_btns.children().last().removeClass('mr-4');
+    }
+
+    show_summary_placeholder() {
+        this.$summary_wrapper.addClass("d-none");
+        this.$component.find('.no-summary-placeholder').removeClass('d-none');
+    }
+
+    switch_to_post_submit_summary() {
+        // switch to full width view
+        this.$component.removeClass('col-span-6').addClass('col-span-10');
+        this.$summary_wrapper.removeClass('w-66').addClass('w-40');
+
+        // switch place holder with summary container
+        this.$component.find('.no-summary-placeholder').addClass('d-none');
+        this.$summary_wrapper.removeClass('d-none');
+    }
+
+    switch_to_recent_invoice_summary() {
+        // switch full width view with 60% view
+        this.$component.removeClass('col-span-10').addClass('col-span-6');
+        this.$summary_wrapper.removeClass('w-40').addClass('w-66');
+
+        // switch place holder with summary container
+        this.$component.find('.no-summary-placeholder').addClass('d-none');
+        this.$summary_wrapper.removeClass('d-none');
+    }
+
+    get_condition_btn_map(after_submission) {
+        if (after_submission) 
+            return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }];
+        
+        return [
+            { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] },
+            { condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']},
+            { condition: this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt']}
+        ];
+    }
+
+    load_summary_of(doc, after_submission=false) {
+        this.$summary_wrapper.removeClass("d-none");
+        
+        after_submission ?
+            this.switch_to_post_submit_summary() : this.switch_to_recent_invoice_summary();
+
+        this.doc = doc;
+
+        this.attach_basic_info(doc);
+
+        this.attach_items_info(doc);
+
+        this.attach_totals_info(doc);
+
+        this.attach_payments_info(doc);
+
+        const condition_btns_map = this.get_condition_btn_map(after_submission);
+
+        this.add_summary_btns(condition_btns_map);
+    }
+
+    attach_basic_info(doc) {
+        frappe.db.get_value('Customer', this.doc.customer, 'email_id').then(({ message }) => {
+            this.customer_email = message.email_id || '';
+            const upper_section_dom = this.get_upper_section_html(doc);
+            this.$upper_section.html(upper_section_dom);
+        });
+    }
+
+    attach_items_info(doc) {
+        this.$items_summary_container.html('');
+        doc.items.forEach(item => {
+            const item_dom = this.get_item_html(doc, item);
+            this.$items_summary_container.append(item_dom);
+        });
+    }
+
+    attach_payments_info(doc) {
+        this.$payment_summary_container.html('');
+        doc.payments.forEach(p => {
+            if (p.amount) {
+                const payment_dom = this.get_payment_html(doc, p);
+                this.$payment_summary_container.append(payment_dom);
+            }
+        });
+        if (doc.redeem_loyalty_points && doc.loyalty_amount) {
+            const payment_dom = this.get_payment_html(doc, {
+                mode_of_payment: 'Loyalty Points',
+                amount: doc.loyalty_amount,
+            });
+            this.$payment_summary_container.append(payment_dom);
+        }
+    }
+
+    attach_totals_info(doc) {
+        this.$totals_summary_container.html('');
+
+        const discount_dom = this.get_discount_html(doc);
+        const net_total_dom = this.get_net_total_html(doc);
+        const taxes_dom = this.get_taxes_html(doc);
+        const grand_total_dom = this.get_grand_total_html(doc);
+        this.$totals_summary_container.append(discount_dom);
+        this.$totals_summary_container.append(net_total_dom);
+        this.$totals_summary_container.append(taxes_dom);
+        this.$totals_summary_container.append(grand_total_dom);
+    }
+
+}
\ 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
new file mode 100644
index 0000000..e1c54f6
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -0,0 +1,503 @@
+{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
+
+erpnext.PointOfSale.Payment = class {
+	constructor({ events, wrapper }) {
+		this.wrapper = wrapper;
+		this.events = events;
+
+		this.init_component();
+	}
+
+	init_component() {
+        this.prepare_dom();
+        this.initialize_numpad();
+		this.bind_events();
+		this.attach_shortcuts();
+		
+	}
+
+	prepare_dom() {
+		this.wrapper.append(
+            `<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
+				<div class="flex flex-col p-16 pt-8 pb-8 w-full">
+					<div class="text-grey mb-6 payment-section no-select pointer">
+						PAYMENT METHOD<span class="octicon octicon-chevron-down collapse-indicator"></span>
+					</div>
+					<div class="payment-modes flex flex-wrap"></div>
+					<div class="invoice-details-section"></div>
+                    <div class="flex mt-auto justify-center w-full">
+                        <div class="flex flex-col justify-center flex-1 ml-4">
+                            <div class="flex w-full">
+                                <div class="totals-remarks items-end justify-end flex flex-1">
+                                    <div class="remarks text-md-0 text-grey mr-auto"></div>
+                                    <div class="totals flex justify-end pt-4"></div>
+                                </div>
+                                <div class="number-pad w-40 mb-4 ml-8 d-none"></div>
+                            </div>
+                            <div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
+                                Complete Order
+							</div>
+							<div class="order-time flex items-center justify-end mt-2 pt-2 pb-2 w-full text-md-0 text-grey no-select pointer d-none"></div>
+                        </div>
+                    </div>
+                </div>
+            </section>`
+        )
+        this.$component = this.wrapper.find('.payment-section');
+		this.$payment_modes = this.$component.find('.payment-modes');
+		this.$totals_remarks = this.$component.find('.totals-remarks');
+		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');
+	}
+
+	make_invoice_fields_control() {
+		frappe.db.get_doc("POS Settings", undefined).then((doc) => {
+			const fields = doc.invoice_fields;
+			if (!fields.length) return;
+
+			this.$invoice_details_section.html(
+				`<div class="text-grey pb-6 mt-2 pointer no-select">
+					ADDITIONAL INFORMATION<span class="octicon octicon-chevron-down collapse-indicator"></span>
+				</div>
+				<div class="invoice-fields grid grid-cols-2 gap-4 mb-6 d-none"></div>`
+			);
+			this.$invoice_fields = this.$invoice_details_section.find('.invoice-fields');
+			const frm = this.events.get_frm();
+
+			fields.forEach(df => {
+				this.$invoice_fields.append(
+					`<div class="invoice_detail_field ${df.fieldname}-field" data-fieldname="${df.fieldname}"></div>`
+				);
+
+				this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
+					df: { 
+						...df,
+						onchange: function() {
+							frm.set_value(this.df.fieldname, this.value);
+						}
+					},
+					parent: this.$invoice_fields.find(`.${df.fieldname}-field`),
+					render_input: true,
+				});
+				this[`${df.fieldname}_field`].set_value(frm.doc[df.fieldname]);
+			})
+		});
+	}
+
+	initialize_numpad() {
+		const me = this;
+		this.number_pad = new erpnext.PointOfSale.NumberPad({
+			wrapper: this.$numpad,
+			events: {
+				numpad_event: function($btn) {
+					me.on_numpad_clicked($btn);
+				}
+			},
+			cols: 3,
+			keys: [
+				[ 1, 2, 3 ],
+				[ 4, 5, 6 ],
+				[ 7, 8, 9 ],
+				[ '.', 0, 'Delete' ]
+			],
+		})
+
+		this.numpad_value = '';
+	}
+
+	on_numpad_clicked($btn) {
+		const me = this;
+		const button_value = $btn.attr('data-button-value');
+
+		highlight_numpad_btn($btn);
+		this.numpad_value = button_value === 'delete' ? this.numpad_value.slice(0, -1) : this.numpad_value + button_value;
+		this.selected_mode.$input.get(0).focus();
+		this.selected_mode.set_value(this.numpad_value);
+
+		function highlight_numpad_btn($btn) {
+			$btn.addClass('shadow-inner bg-selected');
+			setTimeout(() => {
+				$btn.removeClass('shadow-inner bg-selected');
+			}, 100);
+		}
+	}
+
+	bind_events() {
+		const me = this;
+
+		this.$payment_modes.on('click', '.mode-of-payment', function(e) {
+			const mode_clicked = $(this);
+			// if clicked element doesn't have .mode-of-payment class then return
+			if (!$(e.target).is(mode_clicked)) return;
+
+			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');
+
+			// remove highlight from all mode-of-payments
+			$('.mode-of-payment').removeClass('border-primary');
+
+			if (mode_clicked.hasClass('border-primary')) {
+				// clicked one is selected then unselect it
+				mode_clicked.removeClass('border-primary');
+				me.selected_mode = '';
+				me.toggle_numpad(false);
+			} 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');
+				me.toggle_numpad(true);
+
+				me.selected_mode = me[`${mode}_control`];
+				const doc = me.events.get_frm().doc;
+				me.selected_mode?.$input?.get(0).focus();
+				!me.selected_mode?.get_value() ? me.selected_mode?.set_value(doc.grand_total - doc.paid_amount) : '';
+			}
+		})
+
+		this.$payment_modes.on('click', '.shortcut', function(e) {
+			const value = $(this).attr('data-value');
+			me.selected_mode.set_value(value);
+		})
+
+		// this.$totals_remarks.on('click', '.remarks', () => {
+		// 	this.toggle_remarks_control();
+		// })
+
+		this.$component.on('click', '.submit-order', () => {
+			const doc = this.events.get_frm().doc;
+			const paid_amount = doc.paid_amount;
+			const items = doc.items;
+
+			if (paid_amount == 0 || !items.length) {
+				const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order.")
+				frappe.show_alert({ message, indicator: "orange" });
+				frappe.utils.play_sound("error");
+				return;
+			}
+
+			this.events.submit_invoice();
+		})
+
+		frappe.ui.form.on('POS Invoice', 'paid_amount', (frm) => {
+			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');
+			this.attach_cash_shortcuts(frm.doc);
+			!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').removeClass('d-none');
+		})
+
+		frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
+			const formatted_currency = format_currency(frm.doc.loyalty_amount, frm.doc.currency);
+			this.$payment_modes.find(`.loyalty-amount-amount`).html(formatted_currency);
+		});
+
+		frappe.ui.form.on("Sales Invoice Payment", "amount", (frm, cdt, cdn) => {
+			// for setting correct amount after loyalty points are redeemed
+			const default_mop = locals[cdt][cdn];
+			const mode = default_mop.mode_of_payment.replace(' ', '_').toLowerCase();
+			if (this[`${mode}_control`] && this[`${mode}_control`].get_value() != default_mop.amount) {
+				this[`${mode}_control`].set_value(default_mop.amount);
+			}
+		});
+
+		this.$component.on('click', '.invoice-details-section', function(e) {
+			if ($(e.target).closest('.invoice-fields').length) return;
+
+			me.$payment_modes.addClass('d-none');
+			me.$invoice_fields.toggleClass("d-none");
+			me.toggle_numpad(false);
+		});
+		this.$component.on('click', '.payment-section', () => {
+			this.$invoice_fields.addClass("d-none");
+			this.$payment_modes.toggleClass('d-none');
+			this.toggle_numpad(true);
+		})
+	}
+
+	attach_shortcuts() {
+		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();
+			}
+		});
+
+		frappe.ui.keys.on("tab", () => {
+			const payment_is_visible = this.$component.is(":visible");
+			const mode_of_payments = Array.from(this.$payment_modes.find(".mode-of-payment")).map(m => $(m).attr("data-mode"));
+			let active_mode = this.$payment_modes.find(".border-primary");
+			active_mode = active_mode.length ? active_mode.attr("data-mode") : undefined;
+
+			if (!active_mode) return;
+
+			const mode_index = mode_of_payments.indexOf(active_mode);
+			const next_mode_index = (mode_index + 1) % mode_of_payments.length;
+			const next_mode_to_be_clicked = this.$payment_modes.find(`.mode-of-payment[data-mode="${mode_of_payments[next_mode_index]}"]`);
+
+			if (payment_is_visible && mode_index != next_mode_index) {
+				next_mode_to_be_clicked.click();
+			}
+		});
+	}
+
+	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');
+		}
+	}
+
+	render_payment_section() {
+		this.render_payment_mode_dom();
+		this.make_invoice_fields_control();
+		this.update_totals_section();
+	}
+
+	edit_cart() {
+		this.events.toggle_other_sections(false);
+		this.toggle_component(false);
+	}
+
+	checkout() {
+		this.events.toggle_other_sections(true);
+		this.toggle_component(true);
+
+		this.render_payment_section();
+	}
+
+	toggle_remarks_control() {
+		if (this.$remarks.find('.frappe-control').length) {
+			this.$remarks.html('+ Add Remark');
+		} else {
+			this.$remarks.html('');
+			this[`remark_control`] = frappe.ui.form.make_control({
+				df: {
+					label: __('Remark'),
+					fieldtype: 'Data',
+					onchange: function() {}
+				},
+				parent: this.$totals_remarks.find(`.remarks`),
+				render_input: true,
+			});
+			this[`remark_control`].set_value('');
+		}
+	}
+
+	render_payment_mode_dom() {
+		const doc = this.events.get_frm().doc;
+		const payments = doc.payments;
+		const currency = doc.currency;
+
+		this.$payment_modes.html(
+		   `${
+			   payments.map((p, i) => {
+				const mode = p.mode_of_payment.replace(' ', '_').toLowerCase();
+				const payment_type = p.type;
+				const margin = i % 2 === 0 ? 'pr-2' : 'pl-2';
+				const amount = p.amount > 0 ? format_currency(p.amount, currency) : '';
+
+				return (
+					`<div class="w-half ${margin} bg-white">
+						<div class="mode-of-payment rounded border border-grey text-grey text-md
+								mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="${mode}" data-payment-type="${payment_type}">
+							${p.mode_of_payment}
+							<div class="${mode}-amount pay-amount inline float-right text-bold">${amount}</div>
+							<div class="${mode} mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
+						</div>
+					</div>`
+				)
+			   }).join('')
+		   }`
+		)
+
+		payments.forEach(p => {
+			const mode = p.mode_of_payment.replace(' ', '_').toLowerCase();
+			const me = this;
+			this[`${mode}_control`] = frappe.ui.form.make_control({
+				df: {
+					label: __(`${p.mode_of_payment}`),
+					fieldtype: 'Currency',
+					placeholder: __(`Enter ${p.mode_of_payment} amount.`),
+					onchange: function() {
+						if (this.value || this.value == 0) {
+							frappe.model.set_value(p.doctype, p.name, 'amount', flt(this.value))
+								.then(() => me.update_totals_section());
+
+							const formatted_currency = format_currency(this.value, currency);
+							me.$payment_modes.find(`.${mode}-amount`).html(formatted_currency);
+						}
+					}
+				},
+				parent: this.$payment_modes.find(`.${mode}.mode-of-payment-control`),
+				render_input: true,
+			});
+			this[`${mode}_control`].toggle_label(false);
+			this[`${mode}_control`].set_value(p.amount);
+
+			if (p.default) {
+				setTimeout(() => {
+					this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click();
+				}, 500);
+			}
+		})
+
+		this.render_loyalty_points_payment_mode();
+		
+		this.attach_cash_shortcuts(doc);
+	}
+
+	attach_cash_shortcuts(doc) {
+		const grand_total = doc.grand_total;
+		const currency = doc.currency;
+
+		const shortcuts = this.get_cash_shortcuts(flt(grand_total));
+
+		this.$payment_modes.find('.cash-shortcuts').remove();
+		this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after(
+			`<div class="cash-shortcuts grid grid-cols-3 gap-2 flex-1 text-center text-md-0 mb-2 d-none">
+				${
+					shortcuts.map(s => {
+						return `<div class="shortcut rounded bg-light-grey text-dark-grey pt-2 pb-2 no-select pointer" data-value="${s}">
+									${format_currency(s, currency)}
+								</div>`
+					}).join('')
+				}
+			</div>`
+		)
+	}
+
+	get_cash_shortcuts(grand_total) {
+		let steps = [1, 5, 10];
+		const digits = String(Math.round(grand_total)).length;
+
+		steps = steps.map(x => x * (10 ** (digits - 2)));
+
+		const get_nearest = (amount, x) => {
+			let nearest_x = Math.ceil((amount / x)) * x;
+			return nearest_x === amount ? nearest_x + x : nearest_x;
+		}
+
+		return steps.reduce((finalArr, x) => {
+			let nearest_x = get_nearest(grand_total, x);
+			nearest_x = finalArr.indexOf(nearest_x) != -1 ? nearest_x + x : nearest_x;
+			return [...finalArr, nearest_x];
+		}, []);	
+	}
+
+	render_loyalty_points_payment_mode() {
+		const me = this;
+		const doc = this.events.get_frm().doc;
+		const { loyalty_program, loyalty_points, conversion_factor } = this.events.get_customer_details();
+
+		this.$payment_modes.find(`.mode-of-payment[data-mode="loyalty-amount"]`).parent().remove();
+		
+		if (!loyalty_program) return;
+
+		let description, read_only, max_redeemable_amount;
+		if (!loyalty_points) {
+			description = __(`You don't have enough points to redeem.`);
+			read_only = true;
+		} else {
+			max_redeemable_amount = flt(flt(loyalty_points) * flt(conversion_factor), precision("loyalty_amount", doc))
+			description = __(`You can redeem upto ${format_currency(max_redeemable_amount)}.`);
+			read_only = false;
+		}
+
+		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(
+			`<div class="w-half ${margin} bg-white">
+				<div class="mode-of-payment rounded border border-grey text-grey text-md
+						mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
+					Redeem Loyalty Points
+					<div class="loyalty-amount-amount pay-amount inline float-right text-bold">${amount}</div>
+					<div class="loyalty-amount-name inline float-right text-bold text-md-0 d-none">${loyalty_program}</div>
+					<div class="loyalty-amount mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
+				</div>
+			</div>`
+		)
+
+		this['loyalty-amount_control'] = frappe.ui.form.make_control({
+			df: {
+				label: __('Redeem Loyalty Points'),
+				fieldtype: 'Currency',
+				placeholder: __(`Enter amount to be redeemed.`),
+				options: 'company:currency',
+				read_only,
+				onchange: async function() {
+					if (!loyalty_points) return;
+
+					if (this.value > max_redeemable_amount) {
+						frappe.show_alert({
+							message: __(`You cannot redeem more than ${format_currency(max_redeemable_amount)}.`),
+							indicator: "red"
+						});
+						frappe.utils.play_sound("submit");
+						me['loyalty-amount_control'].set_value(0);
+						return;
+					}
+					const redeem_loyalty_points = this.value > 0 ? 1 : 0;
+					await frappe.model.set_value(doc.doctype, doc.name, 'redeem_loyalty_points', redeem_loyalty_points);
+					frappe.model.set_value(doc.doctype, doc.name, 'loyalty_points', parseInt(this.value / conversion_factor));
+				},
+				description
+			},
+			parent: this.$payment_modes.find(`.loyalty-amount.mode-of-payment-control`),
+			render_input: true,
+		});
+		this['loyalty-amount_control'].toggle_label(false);
+
+		// this.render_add_payment_method_dom();
+	}
+
+	render_add_payment_method_dom() {
+		const docstatus = this.events.get_frm().doc.docstatus;
+		if (docstatus === 0)
+			this.$payment_modes.append(
+				`<div class="w-full pr-2">
+					<div class="add-mode-of-payment w-half text-grey mb-4 no-select pointer">+ Add Payment Method</div>
+				</div>`
+			)
+	}
+
+	update_totals_section(doc) {
+		if (!doc) doc = this.events.get_frm().doc;
+		const paid_amount = doc.paid_amount;
+		const remaining = doc.grand_total - doc.paid_amount;
+		const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined;
+		const currency = doc.currency
+		const label = change ? __('Change') : __('To Be Paid');
+
+		this.$totals.html(
+			`<div>
+				<div class="pr-8 border-r-grey">Paid Amount</div>
+				<div class="pr-8 border-r-grey text-bold text-2xl">${format_currency(paid_amount, currency)}</div>
+			</div>
+			<div>
+				<div class="pl-8">${label}</div>
+				<div class="pl-8 text-green-400 text-bold text-2xl">${format_currency(change || remaining, currency)}</div>
+			</div>`
+		)
+	}
+
+	toggle_component(show) {
+		show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+    }
+ }
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js b/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js
deleted file mode 100644
index 79d1700..0000000
--- a/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js
+++ /dev/null
@@ -1,38 +0,0 @@
-QUnit.test("test:Point of Sales", function(assert) {
-	assert.expect(1);
-	let done = assert.async();
-
-	frappe.run_serially([
-		() => frappe.set_route('point-of-sale'),
-		() => frappe.timeout(3),
-		() => frappe.set_control('customer', 'Test Customer 1'),
-		() => frappe.timeout(0.2),
-		() => cur_frm.set_value('customer', 'Test Customer 1'),
-		() => frappe.timeout(2),
-		() => frappe.click_link('Test Product 2'),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.cart-items [data-item-code="Test Product 2"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.number-pad [data-value="Rate"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.number-pad [data-value="2"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.number-pad [data-value="5"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.number-pad [data-value="0"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.number-pad [data-value="Pay"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.frappe-control [data-value="4"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.frappe-control [data-value="5"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_element(`.frappe-control [data-value="0"]`),
-		() => frappe.timeout(0.2),
-		() => frappe.click_button('Submit'),
-		() => frappe.click_button('Yes'),
-		() => frappe.timeout(3),
-		() => assert.ok(cur_frm.doc.docstatus==1, "Sales invoice created successfully"),
-		() => done()
-	]);
-});
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/__init__.py
similarity index 100%
rename from erpnext/selling/doctype/pos_closing_voucher/__init__.py
rename to erpnext/selling/print_format/__init__.py
diff --git a/erpnext/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/gst_pos_invoice/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/pos_closing_voucher/__init__.py
copy to erpnext/selling/print_format/gst_pos_invoice/__init__.py
diff --git a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
new file mode 100644
index 0000000..9094a07b
--- /dev/null
+++ b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
@@ -0,0 +1,23 @@
+{
+ "align_labels_right": 0,
+ "creation": "2017-08-08 12:33:04.773099",
+ "custom_format": 1,
+ "disabled": 0,
+ "doc_type": "POS Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t    {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t    {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2020-04-29 16:47:02.743246",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "GST POS Invoice",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 0,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/pos_invoice/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/pos_closing_voucher/__init__.py
copy to erpnext/selling/print_format/pos_invoice/__init__.py
diff --git a/erpnext/selling/print_format/pos_invoice/pos_invoice.json b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
new file mode 100644
index 0000000..99094ed
--- /dev/null
+++ b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
@@ -0,0 +1,22 @@
+{
+ "align_labels_right": 0,
+ "creation": "2011-12-21 11:08:55",
+ "custom_format": 1,
+ "disabled": 0,
+ "doc_type": "POS Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t    {% if '%' in row.description %}\n\t\t\t\t\t    {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t    {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "idx": 1,
+ "line_breaks": 0,
+ "modified": "2020-04-29 16:45:58.942375",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "POS Invoice",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 0,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/return_pos_invoice/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/pos_closing_voucher/__init__.py
copy to erpnext/selling/print_format/return_pos_invoice/__init__.py
diff --git a/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json b/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json
new file mode 100644
index 0000000..d7f3350
--- /dev/null
+++ b/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json
@@ -0,0 +1,24 @@
+{
+ "align_labels_right": 0,
+ "creation": "2020-05-14 17:02:44.207166",
+ "custom_format": 1,
+ "default_print_language": "en",
+ "disabled": 0,
+ "doc_type": "POS Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Return Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Original Invoice\") }}:</b> {{ doc.return_against }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t    {% if '%' in row.description %}\n\t\t\t\t\t    {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t    {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc)}}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\")}}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2020-05-14 17:13:29.354015",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Return POS Invoice",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 0,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 4a7dd5a..333a563 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -142,7 +142,7 @@
 		frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
 
 		// check if child doctype is Sales Order Item/Qutation Item and calculate the rate
-		if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), cdt)
+		if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item", "POS Invoice Item"]), cdt)
 			this.apply_pricing_rule_on_item(item);
 		else
 			item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0),
@@ -312,6 +312,11 @@
 	batch_no: function(doc, cdt, cdn) {
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
+
+		if (item.serial_no) {
+			return;
+		}
+
 		item.serial_no = null;
 		var has_serial_no;
 		frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_serial_no', (r) => {