From 49cca7ac76f85f1e7449468d1fb6dae0cb249899 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 25 Jan 2019 16:41:01 +0530 Subject: [PATCH 1/4] fix: Customer based price_list in online POS --- erpnext/public/js/controllers/transaction.js | 2 +- erpnext/selling/page/point_of_sale/point_of_sale.js | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 4ef8b2e8be..20e1098806 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -477,7 +477,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var me = this; var item = frappe.get_doc(cdt, cdn); - if (item.serial_no) { + if (item && item.serial_no) { if (!item.item_code) { this.frm.trigger("item_code", cdt, cdn); } 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 1ee5971271..8b4d684466 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -164,6 +164,12 @@ erpnext.pos.PointOfSale = class PointOfSale { } } }); + + frappe.ui.form.on('Sales Invoice', 'selling_price_list', (frm) => { + if(this.items) { + this.items.reset_items(); + } + }) } toggle_editing(flag) { @@ -1384,6 +1390,7 @@ class POSItems { } 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", @@ -1391,10 +1398,10 @@ class POSItems { args: { start, page_length, - 'price_list': this.frm.doc.selling_price_list, + price_list, item_group, search_value, - 'pos_profile': this.frm.doc.pos_profile + pos_profile: this.frm.doc.pos_profile } }).then(r => { // const { items, serial_no, batch_no } = r.message; From c2ffcc5e71a0112c0044a023200263a783fe4beb Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 27 Jan 2019 16:09:07 +0530 Subject: [PATCH 2/4] feat(POS): Customer wise price list in offline mode and enhancement in POS closing voucher --- erpnext/accounts/doctype/sales_invoice/pos.py | 16 ++ erpnext/accounts/page/pos/pos.js | 31 ++- .../pos_closing_voucher.js | 14 ++ .../pos_closing_voucher.json | 194 +++++++++++++++++- 4 files changed, 249 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 287da08ef5..74ec0b30c7 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -55,6 +55,7 @@ def get_pos_data(): 'barcode_data': get_barcode_data(items_list), 'tax_data': get_item_tax_data(), 'price_list_data': get_price_list_data(doc.selling_price_list), + 'customer_wise_price_list': get_customer_wise_price_list(), 'bin_data': get_bin_data(pos_profile), 'pricing_rules': get_pricing_rule_data(doc), 'print_template': print_template, @@ -328,6 +329,21 @@ def get_price_list_data(selling_price_list): return itemwise_price_list +def get_customer_wise_price_list(): + customer_wise_price = {} + customer_price_list_mapping = frappe._dict(frappe.get_all('Customer',fields = ['default_price_list', 'name'], as_list=1)) + + price_lists = frappe.db.sql(""" Select ifnull(price_list_rate, 0) as price_list_rate, + item_code, price_list from `tabItem Price` """, as_dict=1) + + for item in price_lists: + if item.price_list and customer_price_list_mapping.get(item.price_list): + + customer_wise_price.setdefault(customer_price_list_mapping.get(item.price_list),{}).setdefault( + item.item_code, item.price_list_rate + ) + + return customer_wise_price def get_bin_data(pos_profile): itemwise_bin_data = {} diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index c89035c33f..17d6d0ddfc 100755 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -125,7 +125,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.page.add_menu_item(__("Cashier Closing"), function () { frappe.set_route('List', 'Cashier Closing'); - }); + }); this.page.add_menu_item(__("POS Profile"), function () { frappe.set_route('List', 'POS Profile'); @@ -313,6 +313,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.contacts = r.message.contacts; this.address = r.message.address || {}; this.price_list_data = r.message.price_list_data; + this.customer_wise_price_list = r.message.customer_wise_price_list this.bin_data = r.message.bin_data; this.pricing_rules = r.message.pricing_rules; this.print_template = r.message.print_template; @@ -798,6 +799,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ if (item.action) { $(this).val(""); } + me.make_item_list(item.customer_name); }); }, @@ -1037,7 +1039,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.numeric_keypad.show(); }, - make_item_list: function () { + make_item_list: function (customer=null) { var me = this; if (!this.price_list) { frappe.msgprint(__("Price List not found or disabled")); @@ -1051,10 +1053,17 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ if (this.items.length > 0) { $.each(this.items, function(index, obj) { + let customer_price_list = me.customer_wise_price_list[customer]; + let item_price + if (customer && customer_price_list && customer_price_list[obj.name]) { + item_price = format_currency(customer_price_list[obj.name], me.frm.doc.currency); + } else { + item_price = format_currency(me.price_list_data[obj.name], me.frm.doc.currency); + } if(index < me.page_len) { $(frappe.render_template("pos_item", { item_code: obj.name, - item_price: format_currency(me.price_list_data[obj.name], me.frm.doc.currency), + item_price: item_price, item_name: obj.name === obj.item_name ? "" : obj.item_name, item_image: obj.image, item_stock: __('Stock Qty') + ": " + me.get_actual_qty(obj), @@ -1417,8 +1426,20 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.child.income_account = this.pos_profile_data['income_account'] || this.items[0].income_account; this.child.warehouse = (this.item_serial_no[this.child.item_code] ? this.item_serial_no[this.child.item_code][1] : (this.pos_profile_data['warehouse'] || this.items[0].default_warehouse)); - this.child.price_list_rate = flt(this.price_list_data[this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9); - this.child.rate = flt(this.price_list_data[this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9); + + customer = this.frm.doc.customer; + let rate + + customer_price_list = this.customer_wise_price_list[customer] + if (customer_price_list && customer_price_list[this.child.item_code]){ + rate = flt(this.customer_wise_price_list[customer][this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9); + } + else{ + rate = flt(this.price_list_data[this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9); + } + + this.child.price_list_rate = rate; + this.child.rate = rate; this.child.actual_qty = me.get_actual_qty(this.items[0]); this.child.amount = flt(this.child.qty) * flt(this.child.rate); this.child.batch_no = this.item_batch_no[this.child.item_code]; diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js index 67ff8cb649..13bd87867b 100644 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js +++ b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js @@ -20,6 +20,16 @@ frappe.ui.form.on('POS Closing Voucher', { }; }); }, + + 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); }, @@ -47,6 +57,10 @@ frappe.ui.form.on('POS Closing Voucher Details', { } }); +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) { diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json index e24239526a..0ebea83e60 100644 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json +++ b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -307,6 +308,197 @@ "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, @@ -766,7 +958,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 16:15:54.363636", + "modified": "2019-01-26 13:23:49.192650", "modified_by": "Administrator", "module": "Selling", "name": "POS Closing Voucher", From 0bee62b460865fda32ad5f1d2a32ec1c387402e2 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 28 Jan 2019 12:45:11 +0530 Subject: [PATCH 3/4] fix: Made period fields read only and added validation for multiple entries by same user --- .../pos_closing_voucher/pos_closing_voucher.js | 1 + .../pos_closing_voucher.json | 6 +++--- .../pos_closing_voucher/pos_closing_voucher.py | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js index 13bd87867b..f24caf767f 100644 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js +++ b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js @@ -76,6 +76,7 @@ var get_closing_voucher_details = function(frm) { 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 index 0ebea83e60..2ac57794b4 100644 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json +++ b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json @@ -39,7 +39,7 @@ "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, @@ -72,7 +72,7 @@ "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, @@ -958,7 +958,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-01-26 13:23:49.192650", + "modified": "2019-01-28 12:33:45.217813", "modified_by": "Administrator", "module": "Selling", "name": "POS Closing Voucher", diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py index e7fc85e05a..c45571f57c 100644 --- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py +++ b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py @@ -4,6 +4,7 @@ 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 @@ -26,6 +27,7 @@ class POSClosingVoucher(Document): 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) @@ -36,6 +38,21 @@ class POSClosingVoucher(Document): 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: From f0cfe98860b468a7ca72c6094d871a630bf4adec Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 28 Jan 2019 17:58:44 +0530 Subject: [PATCH 4/4] fix: Added missing semicolon and removed null from parameter --- erpnext/accounts/page/pos/pos.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 17d6d0ddfc..1dcbdc6789 100755 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -1039,7 +1039,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.numeric_keypad.show(); }, - make_item_list: function (customer=null) { + make_item_list: function (customer) { var me = this; if (!this.price_list) { frappe.msgprint(__("Price List not found or disabled")); @@ -1428,7 +1428,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ ? this.item_serial_no[this.child.item_code][1] : (this.pos_profile_data['warehouse'] || this.items[0].default_warehouse)); customer = this.frm.doc.customer; - let rate + let rate; customer_price_list = this.customer_wise_price_list[customer] if (customer_price_list && customer_price_list[this.child.item_code]){