From 801029e055825df797a48c188e3130d4cc7fd091 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Nov 2016 00:14:21 +0530 Subject: [PATCH 1/3] Added item groups, customer groups in the POS profile. --- .../doctype/pos_customer_group/__init__.py | 0 .../pos_customer_group.json | 66 +++++++++ .../pos_customer_group/pos_customer_group.py | 10 ++ .../doctype/pos_item_group/__init__.py | 0 .../pos_item_group/pos_item_group.json | 66 +++++++++ .../doctype/pos_item_group/pos_item_group.py | 10 ++ .../doctype/pos_profile/pos_profile.js | 24 +++ .../doctype/pos_profile/pos_profile.json | 140 ++++++++++++++---- .../doctype/pos_profile/pos_profile.py | 11 ++ erpnext/accounts/doctype/sales_invoice/pos.py | 35 +++-- erpnext/accounts/page/pos/pos.js | 4 +- 11 files changed, 322 insertions(+), 44 deletions(-) create mode 100644 erpnext/accounts/doctype/pos_customer_group/__init__.py create mode 100644 erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json create mode 100644 erpnext/accounts/doctype/pos_customer_group/pos_customer_group.py create mode 100644 erpnext/accounts/doctype/pos_item_group/__init__.py create mode 100644 erpnext/accounts/doctype/pos_item_group/pos_item_group.json create mode 100644 erpnext/accounts/doctype/pos_item_group/pos_item_group.py diff --git a/erpnext/accounts/doctype/pos_customer_group/__init__.py b/erpnext/accounts/doctype/pos_customer_group/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json new file mode 100644 index 00000000000..4f6a675fb66 --- /dev/null +++ b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json @@ -0,0 +1,66 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-11-16 15:27:16.413449", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "customer_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Customer Group", + "length": 0, + "no_copy": 0, + "options": "Customer Group", + "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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2016-11-16 15:27:25.730507", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Customer Group", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.py b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.py new file mode 100644 index 00000000000..85c1c9f8ddf --- /dev/null +++ b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class POSCustomerGroup(Document): + pass diff --git a/erpnext/accounts/doctype/pos_item_group/__init__.py b/erpnext/accounts/doctype/pos_item_group/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/pos_item_group/pos_item_group.json b/erpnext/accounts/doctype/pos_item_group/pos_item_group.json new file mode 100644 index 00000000000..b278765234c --- /dev/null +++ b/erpnext/accounts/doctype/pos_item_group/pos_item_group.json @@ -0,0 +1,66 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-11-16 15:26:47.706713", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2016-11-16 15:27:32.263630", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Item Group", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_item_group/pos_item_group.py b/erpnext/accounts/doctype/pos_item_group/pos_item_group.py new file mode 100644 index 00000000000..ceaa57ba60c --- /dev/null +++ b/erpnext/accounts/doctype/pos_item_group/pos_item_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class POSItemGroup(Document): + pass diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js index c1aa0c3cb52..bbbab73e1c2 100755 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.js +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js @@ -26,6 +26,30 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) { }); }); +frappe.ui.form.on("POS Profile", { + setup: function(frm) { + frm.trigger("get_query_for_groups") + }, + + get_query_for_groups: function(frm) { + frm.fields_dict['item_groups'].grid.get_field('item_group').get_query = function(frm, cdt, cdn) { + return{ + filters: { + 'is_group': 0 + } + } + } + + frm.fields_dict['customer_groups'].grid.get_field('customer_group').get_query = function(frm, cdt, cdn) { + return{ + filters: { + 'is_group': 0 + } + } + } + } +}) + // Income Account // -------------------------------- cur_frm.fields_dict['income_account'].get_query = function(doc,cdt,cdn) { diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index f4501218175..e6cfd7409bf 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -375,6 +375,114 @@ "set_only_once": 0, "unique": 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_list_view": 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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_groups", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Item Groups", + "length": 0, + "no_copy": 0, + "options": "POS Item Group", + "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, + "unique": 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_list_view": 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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "customer_groups", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Customer Groups", + "length": 0, + "no_copy": 0, + "options": "POS Customer Group", + "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, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -543,34 +651,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "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, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -951,8 +1031,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-03 15:53:33.820428", - "modified_by": "Administrator", + "modified": "2016-11-17 00:20:51.377850", + "modified_by": "rohit@erpnext.com", "module": "Accounts", "name": "POS Profile", "owner": "Administrator", diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 5f4d5bc75da..ef497bfe29f 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -13,6 +13,7 @@ class POSProfile(Document): def validate(self): self.check_for_duplicate() self.validate_all_link_fields() + self.validate_duplicate_groups() def check_for_duplicate(self): res = frappe.db.sql("""select name, user from `tabPOS Profile` @@ -37,6 +38,16 @@ class POSProfile(Document): "company": self.company, "name": link_dn}): frappe.throw(_("{0} does not belong to Company {1}").format(link_dn, self.company)) + def validate_duplicate_groups(self): + item_groups = [d.item_group for d in self.item_groups] + customer_groups = [d.customer_group for d in self.customer_groups] + + if len(item_groups) != len(set(item_groups)): + frappe.throw(_("Duplicate item group found in the item group table"), title = "Duplicate Item Group") + + if len(customer_groups) != len(set(customer_groups)): + frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group") + def before_save(self): set_account_for_mode_of_payment(self) diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 1d1a1220119..ec30cb00a7d 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -31,7 +31,7 @@ def get_pos_data(): 'doc': doc, 'default_customer': pos_profile.get('customer'), 'items': get_items(doc, pos_profile), - 'customers': get_customers(pos_profile, doc, company_data.default_currency), + 'customers': get_customers_list(pos_profile), 'pricing_rules': get_pricing_rules(doc), 'print_template': print_template, 'meta': { @@ -106,7 +106,7 @@ def update_tax_table(doc): def get_items(doc, pos_profile): item_list = [] - for item in frappe.get_all("Item", fields=["*"], filters={'disabled': 0, 'has_variants': 0, 'is_sales_item': 1}): + for item in get_items_list(pos_profile): item_doc = frappe.get_doc('Item', item.name) if item_doc.taxes: item.taxes = json.dumps(dict(([d.tax_type, d.tax_rate] for d in @@ -129,6 +129,26 @@ def get_items(doc, pos_profile): return item_list +def get_items_list(pos_profile): + cond = "1=1" + item_groups = [] + if pos_profile.get('item_groups'): + cond = "item_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('item_groups')))) + item_groups = [d.item_group for d in pos_profile.get('item_groups')] + + return frappe.db.sql(""" select * from tabItem where disabled = 0 and has_variants = 0 + and is_sales_item = 1 and {cond}""".format(cond=cond), tuple(item_groups), as_dict=1) or [] + +def get_customers_list(pos_profile): + cond = "1=1" + customer_groups = [] + if pos_profile.get('customer_groups'): + cond = "customer_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('customer_groups')))) + customer_groups = [d.customer_group for d in pos_profile.get('customer_groups')] + + return frappe.db.sql(""" select * from tabCustomer where disabled = 0 + and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} + def get_item_warehouse_for_company(company, warehouse): if frappe.db.get_value('Warehouse', warehouse, 'company') != company: warehouse = None @@ -149,17 +169,6 @@ def get_serial_nos(item, pos_profile, company): return serial_no_list -def get_customers(pos_profile, doc, company_currency): - filters = {'disabled': 0} - customer_list = [] - customers = frappe.get_all("Customer", fields=["*"], filters = filters) - - for customer in customers: - customer_currency = get_party_account_currency('Customer', customer.name, doc.company) or doc.currency - if customer_currency == doc.currency or customer_currency == company_currency: - customer_list.append(customer) - return customer_list - def get_pricing_rules(doc): pricing_rules = "" if doc.ignore_pricing_rule == 0: diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 32e9b3ab605..cfc95821e27 100644 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -387,7 +387,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ var $wrap = me.wrapper.find(".item-list"); me.wrapper.find(".item-list").empty(); - if (this.items) { + if (this.items.length > 0) { $.each(this.items, function(index, obj) { if(index < 30){ $(frappe.render_template("pos_item", { @@ -400,6 +400,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ })).tooltip().appendTo($wrap); } }); + } else { + $("

Searching record not found.

").appendTo($wrap) } if(this.items.length == 1 From 2a81960e0bc3d562e8163cc159babf7fb73d7253 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Nov 2016 13:01:52 +0530 Subject: [PATCH 2/3] test cases --- .../doctype/pos_profile/test_pos_profile.py | 45 +++++++++++++++++-- erpnext/accounts/doctype/sales_invoice/pos.py | 4 ++ .../sales_invoice/test_sales_invoice.py | 27 ++--------- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 62274a332f9..9c6a11487c5 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -5,8 +5,47 @@ from __future__ import unicode_literals import frappe import unittest - -# test_records = frappe.get_test_records('POS Profile') +from erpnext.stock.get_item_details import get_pos_profile +from erpnext.accounts.doctype.sales_invoice.pos import get_items_list, get_customers_list class TestPOSProfile(unittest.TestCase): - pass + def test_pos_profile(self): + make_pos_profile() + + pos_profile = get_pos_profile("_Test Company") or {} + if pos_profile: + doc = frappe.get_doc("POS Profile", pos_profile.get("name")) + doc.append('item_groups', {'item_group': '_Test Item Group'}) + doc.append('customer_groups', {'customer_group': '_Test Customer Group'}) + doc.save() + + items = get_items_list(doc) + customers = get_customers_list(doc) + + products_count = frappe.db.sql(""" select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1) + customers_count = frappe.db.sql(""" select count(name) from tabCustomer where customer_group = '_Test Customer Group'""") + + self.assertEquals(len(items), products_count[0][0]) + self.assertEquals(len(customers), customers_count[0][0]) + + frappe.db.sql("delete from `tabPOS Profile`") + +def make_pos_profile(): + pos_profile = frappe.get_doc({ + "company": "_Test Company", + "cost_center": "_Test Cost Center - _TC", + "currency": "INR", + "doctype": "POS Profile", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "income_account": "Sales - _TC", + "name": "_Test POS Profile", + "naming_series": "_T-POS Profile-", + "selling_price_list": "_Test Price List", + "territory": "_Test Territory", + "warehouse": "_Test Warehouse - _TC", + "write_off_account": "_Test Write Off - _TC", + "write_off_cost_center": "_Test Write Off Cost Center - _TC" + }) + + if not frappe.db.exists("POS Profile", "_Test POS Profile"): + pos_profile.insert() \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index ec30cb00a7d..11c68a3e63b 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -133,6 +133,8 @@ def get_items_list(pos_profile): cond = "1=1" item_groups = [] if pos_profile.get('item_groups'): + # Get items based on the item groups defined in the POS profile + cond = "item_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('item_groups')))) item_groups = [d.item_group for d in pos_profile.get('item_groups')] @@ -143,6 +145,8 @@ def get_customers_list(pos_profile): cond = "1=1" customer_groups = [] if pos_profile.get('customer_groups'): + # Get customers based on the customer groups defined in the POS profile + cond = "customer_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('customer_groups')))) customer_groups = [d.customer_group for d in pos_profile.get('customer_groups')] diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 511eeaab9fb..c4f275aba9c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -7,6 +7,7 @@ import unittest, copy from frappe.utils import nowdate, add_days, flt, nowdate from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError @@ -467,7 +468,7 @@ class TestSalesInvoice(unittest.TestCase): def test_pos_gl_entry_with_perpetual_inventory(self): set_perpetual_inventory() - self.make_pos_profile() + make_pos_profile() self._insert_purchase_receipt() pos = copy.deepcopy(test_records[1]) @@ -486,7 +487,7 @@ class TestSalesInvoice(unittest.TestCase): def test_pos_change_amount(self): set_perpetual_inventory() - self.make_pos_profile() + make_pos_profile() self._insert_purchase_receipt() pos = copy.deepcopy(test_records[1]) @@ -508,7 +509,7 @@ class TestSalesInvoice(unittest.TestCase): set_perpetual_inventory() - self.make_pos_profile() + make_pos_profile() self._insert_purchase_receipt() pos = copy.deepcopy(test_records[1]) @@ -572,26 +573,6 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") - def make_pos_profile(self): - pos_profile = frappe.get_doc({ - "company": "_Test Company", - "cost_center": "_Test Cost Center - _TC", - "currency": "INR", - "doctype": "POS Profile", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "income_account": "Sales - _TC", - "name": "_Test POS Profile", - "naming_series": "_T-POS Profile-", - "selling_price_list": "_Test Price List", - "territory": "_Test Territory", - "warehouse": "_Test Warehouse - _TC", - "write_off_account": "_Test Write Off - _TC", - "write_off_cost_center": "_Test Write Off Cost Center - _TC" - }) - - if not frappe.db.exists("POS Profile", "_Test POS Profile"): - pos_profile.insert() - def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self): set_perpetual_inventory() From a27c417e48a60b7ccc314c7dfb3068f6a4055cae Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 20 Nov 2016 23:41:13 +0530 Subject: [PATCH 3/3] fix master data sync performance issue --- erpnext/accounts/doctype/sales_invoice/pos.py | 132 ++++++++++++------ erpnext/accounts/page/pos/pos.js | 80 +++++++---- 2 files changed, 139 insertions(+), 73 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 11c68a3e63b..e5c86dc4360 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -30,10 +30,16 @@ def get_pos_data(): return { 'doc': doc, 'default_customer': pos_profile.get('customer'), - 'items': get_items(doc, pos_profile), + 'items': get_items_list(pos_profile), 'customers': get_customers_list(pos_profile), - 'pricing_rules': get_pricing_rules(doc), + 'serial_no_data': get_serial_no_data(pos_profile, doc.company), + 'batch_no_data': get_batch_no_data(), + 'tax_data': get_item_tax_data(), + 'price_list_data': get_price_list_data(doc.selling_price_list), + 'bin_data': get_bin_data(pos_profile), + 'pricing_rules': get_pricing_rule_data(doc), 'print_template': print_template, + 'pos_profile': pos_profile, 'meta': { 'invoice': frappe.get_meta('Sales Invoice'), 'items': frappe.get_meta('Sales Invoice Item'), @@ -104,31 +110,6 @@ def update_tax_table(doc): for tax in taxes: doc.append('taxes', tax) -def get_items(doc, pos_profile): - item_list = [] - for item in get_items_list(pos_profile): - item_doc = frappe.get_doc('Item', item.name) - if item_doc.taxes: - item.taxes = json.dumps(dict(([d.tax_type, d.tax_rate] for d in - item_doc.get("taxes")))) - - item.price_list_rate = frappe.db.get_value('Item Price', {'item_code': item.name, - 'price_list': doc.selling_price_list}, 'price_list_rate') or 0 - item.default_warehouse = pos_profile.get('warehouse') or \ - get_item_warehouse_for_company(doc.company, item.default_warehouse) or None - item.expense_account = pos_profile.get('expense_account') or item.expense_account - item.income_account = pos_profile.get('income_account') or item_doc.income_account - item.cost_center = pos_profile.get('cost_center') or item_doc.selling_cost_center - item.actual_qty = frappe.db.get_value('Bin', {'item_code': item.name, - 'warehouse': item.default_warehouse}, 'actual_qty') or 0 - item.serial_nos = get_serial_nos(item, pos_profile, doc.company) - item.batch_nos = frappe.db.sql_list("""select name from `tabBatch` where ifnull(expiry_date, '4000-10-10') > curdate() - and item = %(item_code)s""", {'item_code': item.item_code}) - - item_list.append(item) - - return item_list - def get_items_list(pos_profile): cond = "1=1" item_groups = [] @@ -138,8 +119,15 @@ def get_items_list(pos_profile): cond = "item_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('item_groups')))) item_groups = [d.item_group for d in pos_profile.get('item_groups')] - return frappe.db.sql(""" select * from tabItem where disabled = 0 and has_variants = 0 - and is_sales_item = 1 and {cond}""".format(cond=cond), tuple(item_groups), as_dict=1) or [] + return frappe.db.sql(""" + select + name, item_code, item_name, description, item_group, expense_account, has_batch_no, + has_serial_no, expense_account, selling_cost_center, stock_uom, image, default_warehouse + from + tabItem + where + disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond} + """.format(cond=cond), tuple(item_groups), as_dict=1) def get_customers_list(pos_profile): cond = "1=1" @@ -150,30 +138,88 @@ def get_customers_list(pos_profile): cond = "customer_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('customer_groups')))) customer_groups = [d.customer_group for d in pos_profile.get('customer_groups')] - return frappe.db.sql(""" select * from tabCustomer where disabled = 0 + return frappe.db.sql(""" select name, customer_name, customer_group, + territory from tabCustomer where disabled = 0 and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} -def get_item_warehouse_for_company(company, warehouse): - if frappe.db.get_value('Warehouse', warehouse, 'company') != company: - warehouse = None - return warehouse +def get_serial_no_data(pos_profile, company): + # get itemwise serial no data + # example {'Nokia Lumia 1020': {'SN0001': 'Pune'}} + # where Nokia Lumia 1020 is item code, SN0001 is serial no and Pune is warehouse -def get_serial_nos(item, pos_profile, company): cond = "1=1" if pos_profile.get('update_stock') and pos_profile.get('warehouse'): cond = "warehouse = '{0}'".format(pos_profile.get('warehouse')) - serial_nos = frappe.db.sql("""select name, warehouse from `tabSerial No` where {0} - and item_code = %(item_code)s and company = %(company)s - """.format(cond), {'item_code': item.item_code, 'company': company}, as_dict=1) + serial_nos = frappe.db.sql("""select name, warehouse, item_code from `tabSerial No` where {0} + and company = %(company)s """.format(cond), {'company': company}, as_dict=1) - serial_no_list = {} - for serial_no in serial_nos: - serial_no_list[serial_no.name] = serial_no.warehouse + itemwise_serial_no = {} + for sn in serial_nos: + if sn.item_code not in itemwise_serial_no: + itemwise_serial_no.setdefault(sn.item_code, {}) + itemwise_serial_no[sn.item_code][sn.name] = sn.warehouse - return serial_no_list + return itemwise_serial_no -def get_pricing_rules(doc): +def get_batch_no_data(): + # get itemwise batch no data + # exmaple: {'LED-GRE': [Batch001, Batch002]} + # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse + + itemwise_batch = {} + batches = frappe.db.sql("""select name, item from `tabBatch` + where ifnull(expiry_date, '4000-10-10') >= curdate()""", as_dict=1) + + for batch in batches: + if batch.item not in itemwise_batch: + itemwise_batch.setdefault(batch.item, []) + itemwise_batch[batch.item].append(batch.name) + + return itemwise_batch + +def get_item_tax_data(): + # get default tax of an item + # example: {'Consulting Services': {'Excise 12 - TS': '12.000'}} + + itemwise_tax = {} + taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax`""", as_dict=1) + + for tax in taxes: + if tax.parent not in itemwise_tax: + itemwise_tax.setdefault(tax.parent, {}) + itemwise_tax[tax.parent][tax.tax_type] = tax.tax_rate + + return itemwise_tax + +def get_price_list_data(selling_price_list): + itemwise_price_list = {} + price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate, + item_code from `tabItem Price` ip where price_list = %(price_list)s""", + {'price_list': selling_price_list}, as_dict=1) + + for item in price_lists: + itemwise_price_list[item.item_code] = item.price_list_rate + + return itemwise_price_list + +def get_bin_data(pos_profile): + itemwise_bin_data = {} + cond = "1=1" + if pos_profile.get('warehouse'): + cond = "warehouse = '{0}'".format(pos_profile.get('warehouse')) + + bin_data = frappe.db.sql(""" select item_code, warehouse, actual_qty from `tabBin` + where actual_qty > 0 and {cond}""".format(cond=cond), as_dict=1) + + for bins in bin_data: + if bins.item_code not in itemwise_bin_data: + itemwise_bin_data.setdefault(bins.item_code, {}) + itemwise_bin_data[bins.item_code][bins.warehouse] = bins.actual_qty + + return itemwise_bin_data + +def get_pricing_rule_data(doc): pricing_rules = "" if doc.ignore_pricing_rule == 0: pricing_rules = frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2 diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index cfc95821e27..26bf3ea54c3 100644 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -25,7 +25,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.set_indicator(); this.onload(); this.make_menu_list(); - this.set_interval_for_si_sync(); this.si_docs = this.get_doc_from_localstorage(); }, @@ -73,8 +72,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.get_data_from_server(function(){ me.create_new(); }); - - this.check_internet_connection(); }, make_menu_list: function(){ @@ -204,13 +201,10 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ freeze: true, freeze_message: __("Master data syncing, it might take some time"), callback: function(r){ - window.items = r.message.items; - window.customers = r.message.customers; - window.pricing_rules = r.message.pricing_rules; - window.meta = r.message.meta; - window.print_template = r.message.print_template; - me.default_customer = r.message.default_customer || null; + me.init_master_data(r) localStorage.setItem('doc', JSON.stringify(r.message.doc)); + me.set_interval_for_si_sync(); + me.check_internet_connection(); if(callback){ callback(); } @@ -218,6 +212,22 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }) }, + init_master_data: function(r){ + var me = this; + this.meta = r.message.meta; + this.item_data = r.message.items; + this.customers = r.message.customers; + this.serial_no_data = r.message.serial_no_data; + this.batch_no_data = r.message.batch_no_data; + this.tax_data = r.message.tax_data; + this.price_list_data = r.message.price_list_data; + this.bin_data = r.message.bin_data; + this.pricing_rules = r.message.pricing_rules; + this.print_template = r.message.print_template; + this.pos_profile_data = r.message.pos_profile; + this.default_customer = r.message.default_customer || null; + }, + save_previous_entry : function(){ if(this.frm.doc.items.length > 0){ this.create_invoice() @@ -233,20 +243,19 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }, load_data: function(load_doc){ - this.items = window.items; - this.customers = window.customers; - this.pricing_rules = window.pricing_rules; + var me = this; + this.items = this.item_data; if(load_doc) { this.frm.doc = JSON.parse(localStorage.getItem('doc')); } - $.each(window.meta, function(i, data){ + $.each(this.meta, function(i, data){ frappe.meta.sync(data) }) this.print_template = frappe.render_template("print_template", - {content: window.print_template, title:"POS", + {content: this.print_template, title:"POS", base_url: frappe.urllib.get_base_url(), print_css: frappe.boot.print_css}) }, @@ -392,7 +401,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ if(index < 30){ $(frappe.render_template("pos_item", { item_code: obj.name, - item_price: format_currency(obj.price_list_rate, me.frm.doc.currency), + item_price: format_currency(me.price_list_data[obj.name], me.frm.doc.currency), item_name: obj.name===obj.item_name ? "" : obj.item_name, item_image: obj.image ? "url('" + obj.image + "')" : null, color: frappe.get_palette(obj.item_name), @@ -428,7 +437,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.item_batch_no = {}; if(item_code){ - return $.grep(window.items, function(item){ + return $.grep(this.item_data, function(item){ if(item.item_code == item_code ){ return true } @@ -441,14 +450,15 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ search_status = true if(key){ - return $.grep(window.items, function(item){ + return $.grep(this.item_data, function(item){ if(search_status){ - if(in_list(item.batch_nos, me.search.$input.val())){ + if(in_list(me.batch_no_data[item.item_code], me.search.$input.val())){ search_status = false; return me.item_batch_no[item.item_code] = me.search.$input.val() - } else if(in_list(Object.keys(item.serial_nos), me.search.$input.val())) { + } else if( me.serial_no_data[item.item_code] + && in_list(Object.keys(me.serial_no_data[item.item_code]), me.search.$input.val())) { search_status = false; - me.item_serial_no[item.item_code] = [me.search.$input.val(), item.serial_nos[me.search.$input.val()]] + me.item_serial_no[item.item_code] = [me.search.$input.val(), me.serial_no_data[item.item_code][me.search.$input.val()]] return true } else if(item.barcode == me.search.$input.val()) { search_status = false; @@ -460,7 +470,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ } }) }else{ - return window.items; + return this.item_data; } }, @@ -613,18 +623,18 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.child.description = this.items[0].description; this.child.qty = 1; this.child.item_group = this.items[0].item_group; - this.child.cost_center = this.items[0].cost_center; - this.child.income_account = this.items[0].income_account; + this.child.cost_center = this.pos_profile_data['cost_center'] || this.items[0].cost_center; + 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.items[0].default_warehouse); - this.child.price_list_rate = flt(this.items[0].price_list_rate, 9) / flt(this.frm.doc.conversion_rate, 9); - this.child.rate = flt(this.items[0].price_list_rate, 9) / flt(this.frm.doc.conversion_rate, 9); - this.child.actual_qty = this.items[0].actual_qty; + ? 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], 9) / flt(this.frm.doc.conversion_rate, 9); + this.child.rate = flt(this.price_list_data[this.child.item_code], 9) / flt(this.frm.doc.conversion_rate, 9); + 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]; this.child.serial_no = (this.item_serial_no[this.child.item_code] ? this.item_serial_no[this.child.item_code][0] : ''); - this.child.item_tax_rate = this.items[0].taxes; + this.child.item_tax_rate = JSON.stringify(this.tax_data[this.child.item_code]); }, update_paid_amount_status: function(update_paid_amount){ @@ -670,7 +680,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ item_code: d.item_code, item_name: (d.item_name===d.item_code || !d.item_name) ? "" : ("
" + d.item_name), qty: d.qty, - actual_qty: d.actual_qty, + actual_qty: me.actual_qty, projected_qty: d.projected_qty, rate: format_number(d.rate, me.frm.doc.currency), amount: format_currency(d.amount, me.frm.doc.currency) @@ -1066,8 +1076,18 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }, validate_warehouse: function(){ - if(!this.items[0].default_warehouse){ + if(!this.items[0].default_warehouse && !this.pos_profile_data['warehouse']){ frappe.throw(__("Default warehouse is required for selected item")) } + }, + + get_actual_qty: function(item) { + this.actual_qty = 0.0; + var warehouse = this.pos_profile_data['warehouse'] || item.default_warehouse; + if(warehouse && this.bin_data[item.item_code]) { + this.actual_qty = this.bin_data[item.item_code][warehouse] || 0; + } + + return this.actual_qty } }) \ No newline at end of file