diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 85e34036fe..282d6e40e7 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '10.1.20' +__version__ = '10.1.21' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ee16e4c64a..b174d9b224 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -334,7 +334,7 @@ class SalesInvoice(SellingController): for item in self.get("items"): if item.get('item_code'): for fname, val in get_pos_profile_item_details(pos, - frappe._dict(item.as_dict()), pos).items(): + frappe._dict(item.as_dict()), pos, True).items(): if (not for_validate) or (for_validate and not item.get(fname)): item.set(fname, val) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index ebef4b4293..7ccd01563d 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -20,6 +20,10 @@ frappe.ui.form.on("Opportunity", { frm.trigger('set_contact_link'); }, + with_items: function(frm) { + frm.trigger('toggle_mandatory'); + }, + customer_address: function(frm, cdt, cdn) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false); }, @@ -35,6 +39,7 @@ frappe.ui.form.on("Opportunity", { var doc = frm.doc; frm.events.enquiry_from(frm); frm.trigger('set_contact_link'); + frm.trigger('toggle_mandatory'); erpnext.toggle_naming_series(); if(!doc.__islocal && doc.status!=="Lost") { @@ -84,6 +89,10 @@ frappe.ui.form.on("Opportunity", { method: "erpnext.crm.doctype.opportunity.opportunity.make_supplier_quotation", frm: cur_frm }) + }, + + toggle_mandatory: function(frm) { + frm.toggle_reqd("items", frm.doc.with_items ? 1:0); } }) diff --git a/erpnext/patches/v9_2/rename_translated_domains_in_en.py b/erpnext/patches/v9_2/rename_translated_domains_in_en.py index c787b84239..fc3d01cc60 100644 --- a/erpnext/patches/v9_2/rename_translated_domains_in_en.py +++ b/erpnext/patches/v9_2/rename_translated_domains_in_en.py @@ -2,6 +2,7 @@ import frappe from frappe import _ def execute(): + frappe.reload_doc('stock', 'doctype', 'item') language = frappe.get_single("System Settings").language if language and language.startswith('en'): return diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index fb2fabace4..b878a1ea5d 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -10,7 +10,7 @@ def validate_gstin_for_india(doc, method): if doc.gstin: doc.gstin = doc.gstin.upper() - if doc.gstin != "NA": + if doc.gstin not in ["NA", "na"]: p = re.compile("[0-9]{2}[a-zA-Z]{5}[0-9]{4}[a-zA-Z]{1}[1-9A-Za-z]{1}[Z]{1}[0-9a-zA-Z]{1}") if not p.match(doc.gstin): frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered")) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index b9594490f5..c27bd478f3 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -65,6 +65,10 @@ frappe.ui.form.on("Sales Order Item", { }); erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend({ + onload: function(doc, dt, dn) { + this._super(); + }, + refresh: function(doc, dt, dn) { var me = this; this._super(); 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 8654287208..31cb38b2c1 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -49,6 +49,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p where i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt}) + and ifnull(i.end_of_life, curdate()) >= curdate() and {condition} limit {start}, {page_length}""".format(start=start, page_length=page_length, lft=lft, rgt=rgt, condition=condition), diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 08fbd33e68..747ed6118a 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -140,7 +140,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 0, + "search_index": 1, "set_only_once": 1, "translatable": 0, "unique": 0 @@ -236,7 +236,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 0, + "search_index": 1, "set_only_once": 0, "translatable": 0, "unique": 0 @@ -3063,7 +3063,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "translatable": 0, "unique": 0 @@ -3561,7 +3561,7 @@ "issingle": 0, "istable": 0, "max_attachments": 1, - "modified": "2018-03-06 10:21:48.715529", + "modified": "2018-04-11 12:21:48.715529", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cafa4bc4c7..eaa3d4d956 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -410,7 +410,7 @@ def get_party_item_code(args, item_doc, out): item_supplier = item_doc.get("supplier_items", {"supplier": args.supplier}) out.supplier_part_no = item_supplier[0].supplier_part_no if item_supplier else None -def get_pos_profile_item_details(company, args, pos_profile=None): +def get_pos_profile_item_details(company, args, pos_profile=None, update_data=False): res = frappe._dict() if not pos_profile: @@ -418,7 +418,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None): if pos_profile: for fieldname in ("income_account", "cost_center", "warehouse", "expense_account"): - if not args.get(fieldname) and pos_profile.get(fieldname): + if (not args.get(fieldname) or update_data) and pos_profile.get(fieldname): res[fieldname] = pos_profile.get(fieldname) if res.get("warehouse"): diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index 4aa240d8dc..90945e9f91 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -9,7 +9,7 @@ frappe.query_reports["Stock Balance"] = { "fieldtype": "Date", "width": "80", "reqd": 1, - "default": frappe.sys_defaults.year_start_date, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), }, { "fieldname":"to_date", @@ -31,7 +31,12 @@ frappe.query_reports["Stock Balance"] = { "label": __("Item"), "fieldtype": "Link", "width": "80", - "options": "Item" + "options": "Item", + "get_query": function() { + return { + query: "erpnext.controllers.queries.item_query" + } + } }, { "fieldname": "warehouse", diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index d6fb87350c..17491ede95 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -4,7 +4,8 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, cint, getdate +from frappe.utils import flt, cint, getdate, now +from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition def execute(filters=None): if not filters: filters = {} @@ -12,10 +13,11 @@ def execute(filters=None): validate_filters(filters) columns = get_columns() - item_map = get_item_details(filters) - item_reorder_detail_map = get_item_reorder_details(filters) - iwb_map = get_item_warehouse_map(filters) - + items = get_items(filters) + sle = get_stock_ledger_entries(filters, items) + iwb_map = get_item_warehouse_map(filters, sle) + item_map = get_item_details(items, sle, filters) + item_reorder_detail_map = get_item_reorder_details(item_map.keys()) data = [] for (company, item, warehouse) in sorted(iwb_map): @@ -88,21 +90,9 @@ def get_conditions(filters): else: frappe.throw(_("'To Date' is required")) - if filters.get("item_group"): - ig_details = frappe.db.get_value("Item Group", filters.get("item_group"), - ["lft", "rgt"], as_dict=1) - - if ig_details: - conditions += """ - and exists (select name from `tabItem Group` ig - where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name) - """ % (ig_details.lft, ig_details.rgt) - - if filters.get("item_code"): - conditions += " and sle.item_code = '%s'" % frappe.db.escape(filters.get("item_code"), percent=False) - if filters.get("warehouse"): - warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1) + warehouse_details = frappe.db.get_value("Warehouse", + filters.get("warehouse"), ["lft", "rgt"], as_dict=1) if warehouse_details: conditions += " and exists (select name from `tabWarehouse` wh \ where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"%(warehouse_details.lft, @@ -110,30 +100,29 @@ def get_conditions(filters): return conditions -def get_stock_ledger_entries(filters): +def get_stock_ledger_entries(filters, items): + item_conditions_sql = '' + if items: + item_conditions_sql = ' and sle.item_code in ({})'\ + .format(', '.join(['"' + frappe.db.escape(i) + '"' for i in items])) + conditions = get_conditions(filters) - - join_table_query = "" - if filters.get("item_group"): - join_table_query = "inner join `tabItem` item on item.name = sle.item_code" - + return frappe.db.sql(""" select sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate, sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference from - `tabStock Ledger Entry` sle force index (posting_sort_index) %s - where sle.docstatus < 2 %s + `tabStock Ledger Entry` sle force index (posting_sort_index) + where sle.docstatus < 2 %s %s order by sle.posting_date, sle.posting_time, sle.name""" % - (join_table_query, conditions), as_dict=1) + (item_conditions_sql, conditions), as_dict=1) -def get_item_warehouse_map(filters): +def get_item_warehouse_map(filters, sle): iwb_map = {} from_date = getdate(filters.get("from_date")) to_date = getdate(filters.get("to_date")) - sle = get_stock_ledger_entries(filters) - for d in sle: key = (d.company, d.item_code, d.warehouse) if key not in iwb_map: @@ -191,20 +180,33 @@ def filter_items_with_no_transactions(iwb_map): return iwb_map -def get_item_details(filters): - condition = '' - value = () +def get_items(filters): + conditions = [] if filters.get("item_code"): - condition = "where item_code=%s" - value = (filters.get("item_code"),) + conditions.append("item.name=%(item_code)s") + else: + if filters.get("brand"): + conditions.append("item.brand=%(brand)s") + if filters.get("item_group"): + conditions.append(get_item_group_condition(filters.get("item_group"))) - items = frappe.db.sql(""" - select name, item_name, stock_uom, item_group, brand, description - from tabItem - {condition} - """.format(condition=condition), value, as_dict=1) + items = [] + if conditions: + items = frappe.db.sql_list("""select name from `tabItem` item where {}""" + .format(" and ".join(conditions)), filters) + return items - item_details = dict((d.name , d) for d in items) +def get_item_details(items, sle, filters): + item_details = {} + if not items: + items = list(set([d.item_code for d in sle])) + + for item in frappe.db.sql(""" + select name, item_name, description, item_group, brand, stock_uom + from `tabItem` + where name in ({0}) + """.format(', '.join(['"' + frappe.db.escape(i) + '"' for i in items])), as_dict=1): + item_details.setdefault(item.name, item) if filters.get('show_variant_attributes', 0) == 1: variant_values = get_variant_values_for(item_details.keys()) @@ -212,18 +214,12 @@ def get_item_details(filters): return item_details -def get_item_reorder_details(filters): - condition = '' - value = () - if filters.get("item_code"): - condition = "where parent=%s" - value = (filters.get("item_code"),) - +def get_item_reorder_details(items): item_reorder_details = frappe.db.sql(""" select parent, warehouse, warehouse_reorder_qty, warehouse_reorder_level from `tabItem Reorder` - {condition} - """.format(condition=condition), value, as_dict=1) + where parent in ({0}) + """.format(', '.join(['"' + frappe.db.escape(i) + '"' for i in items])), as_dict=1) return dict((d.parent + d.warehouse, d) for d in item_reorder_details) @@ -233,12 +229,10 @@ def validate_filters(filters): if sle_count > 500000: frappe.throw(_("Please set filter based on Item or Warehouse")) - def get_variants_attributes(): '''Return all item variant attributes.''' return [i.name for i in frappe.get_all('Item Attribute')] - def get_variant_values_for(items): '''Returns variant values for items.''' attribute_map = {} diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index d4f5ab5414..660357cdc3 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -35,7 +35,12 @@ frappe.query_reports["Stock Ledger"] = { "fieldname":"item_code", "label": __("Item"), "fieldtype": "Link", - "options": "Item" + "options": "Item", + "get_query": function() { + return { + query: "erpnext.controllers.queries.item_query" + } + } }, { "fieldname":"item_group", diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index ef198f0d23..c9286a3e87 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -7,13 +7,12 @@ from frappe import _ def execute(filters=None): columns = get_columns() - item_conditions = get_item_conditions(filters) - item_details = get_item_details(filters, item_conditions) - sl_entries = get_stock_ledger_entries(filters, item_conditions, item_details) + items = get_items(filters) + sl_entries = get_stock_ledger_entries(filters, items) + item_details = get_item_details(items, sl_entries) opening_row = get_opening_balance(filters, columns) data = [] - if opening_row: data.append(opening_row) @@ -53,12 +52,12 @@ def get_columns(): return columns -def get_stock_ledger_entries(filters, item_conditions, item_details): +def get_stock_ledger_entries(filters, items): item_conditions_sql = '' - if item_conditions: - items = ['"' + frappe.db.escape(i) + '"' for i in item_details.keys()] - if items: - item_conditions_sql = 'and sle.item_code in ({})'.format(', '.join(items)) + if items: + item_conditions_sql = 'and sle.item_code in ({})'\ + .format(', '.join(['"' + frappe.db.escape(i) + '"' for i in items])) + return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date, item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate, stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project @@ -73,25 +72,35 @@ def get_stock_ledger_entries(filters, item_conditions, item_details): item_conditions_sql = item_conditions_sql ), filters, as_dict=1) -def get_item_details(filters, item_conditions): - item_details = {} - for item in frappe.db.sql("""select name, item_name, description, item_group, - brand, stock_uom from `tabItem` item {item_conditions}"""\ - .format(item_conditions=item_conditions), filters, as_dict=1): - item_details.setdefault(item.name, item) - - return item_details - -def get_item_conditions(filters): +def get_items(filters): conditions = [] if filters.get("item_code"): conditions.append("item.name=%(item_code)s") - if filters.get("brand"): - conditions.append("item.brand=%(brand)s") - if filters.get("item_group"): - conditions.append(get_item_group_condition(filters.get("item_group"))) + else: + if filters.get("brand"): + conditions.append("item.brand=%(brand)s") + if filters.get("item_group"): + conditions.append(get_item_group_condition(filters.get("item_group"))) - return "where {}".format(" and ".join(conditions)) if conditions else "" + items = [] + if conditions: + items = frappe.db.sql_list("""select name from `tabItem` item where {}""" + .format(" and ".join(conditions)), filters) + return items + +def get_item_details(items, sl_entries): + item_details = {} + if not items: + items = list(set([d.item_code for d in sl_entries])) + + for item in frappe.db.sql(""" + select name, item_name, description, item_group, brand, stock_uom + from `tabItem` + where name in ({0}) + """.format(', '.join(['"' + frappe.db.escape(i) + '"' for i in items])), as_dict=1): + item_details.setdefault(item.name, item) + + return item_details def get_sle_conditions(filters): conditions = [] diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js index 2dbbc5b21c..937c0a2572 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js @@ -19,7 +19,12 @@ frappe.query_reports["Stock Projected Qty"] = { "fieldname":"item_code", "label": __("Item"), "fieldtype": "Link", - "options": "Item" + "options": "Item", + "get_query": function() { + return { + query: "erpnext.controllers.queries.item_query" + } + } }, { "fieldname":"brand", diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 1c4639b4b3..d2ec7c30a9 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -41,6 +41,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -71,6 +72,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 1, + "translatable": 0, "unique": 0 }, { @@ -100,6 +102,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -131,6 +134,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -159,6 +163,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -192,6 +197,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -225,6 +231,39 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "email_account", + "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": "Email Account", + "length": 0, + "no_copy": 0, + "options": "Email Account", + "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 }, { @@ -257,6 +296,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -287,6 +327,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -319,6 +360,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -349,6 +391,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -379,6 +422,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -408,6 +452,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -438,6 +483,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -468,6 +514,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -498,6 +545,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -527,6 +575,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -558,6 +607,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -589,6 +639,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -619,6 +670,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -649,6 +701,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -681,6 +734,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -711,6 +765,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -743,6 +798,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -774,6 +830,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -806,6 +863,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -835,6 +893,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -865,6 +924,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -879,7 +939,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-01-11 07:10:53.707415", + "modified": "2018-04-13 13:03:14.748090", "modified_by": "Administrator", "module": "Support", "name": "Issue", @@ -887,7 +947,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1,