Merge pull request #21135 from marination/stock-ledger-typo

fix: Typo in stock level validation in Stock Ledger
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index ba1ceff..a421662 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -437,13 +437,17 @@
 					if (not for_validate) or (for_validate and not self.get(fieldname)):
 						self.set(fieldname, pos.get(fieldname))
 
-			customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
-
 			if pos.get("company_address"):
 				self.company_address = pos.get("company_address")
 
-			if not customer_price_list:
-				self.set('selling_price_list', pos.get('selling_price_list'))
+			customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
+
+			customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list')
+
+			selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
+
+			if selling_price_list:
+				self.set('selling_price_list', selling_price_list)
 
 			if not for_validate:
 				self.update_stock = cint(pos.get("update_stock"))
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 8567740..4cfeb25 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -11,7 +11,7 @@
 	add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day)
 from frappe.contacts.doctype.address.address import (get_address_display,
 	get_default_address, get_company_address)
-from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact
+from frappe.contacts.doctype.contact.contact import get_contact_details
 from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext import get_company_currency
@@ -613,3 +613,26 @@
 
 	if data:
 		return frappe._dict(data)
+
+def get_default_contact(doctype, name):
+	"""
+		Returns default contact for the given doctype and name.
+		Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
+	"""
+	out = frappe.db.sql("""
+			SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
+			FROM `tabDynamic Link` dl
+			INNER JOIN tabContact c ON c.name = dl.parent
+			WHERE
+				dl.link_doctype=%s AND
+				dl.link_name=%s AND
+				dl.parenttype = "Contact"
+			ORDER BY is_primary_contact DESC, is_billing_contact DESC
+		""", (doctype, name))
+	if out:
+		try:
+			return out[0][0]
+		except:
+			return None
+	else:
+		return None
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/custom/contact.json b/erpnext/erpnext_integrations/custom/contact.json
new file mode 100644
index 0000000..98a4bbc
--- /dev/null
+++ b/erpnext/erpnext_integrations/custom/contact.json
@@ -0,0 +1,60 @@
+{
+ "custom_fields": [
+  {
+   "_assign": null,
+   "_comments": null,
+   "_liked_by": null,
+   "_user_tags": null,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "collapsible_depends_on": null,
+   "columns": 0,
+   "creation": "2019-12-02 11:00:03.432994",
+   "default": null,
+   "depends_on": null,
+   "description": null,
+   "docstatus": 0,
+   "dt": "Contact",
+   "fetch_from": null,
+   "fetch_if_empty": 0,
+   "fieldname": "is_billing_contact",
+   "fieldtype": "Check",
+   "hidden": 0,
+   "idx": 27,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "insert_after": "is_primary_contact",
+   "label": "Is Billing Contact",
+   "length": 0,
+   "modified": "2019-12-02 11:00:03.432994",
+   "modified_by": "Administrator",
+   "name": "Contact-is_billing_contact",
+   "no_copy": 0,
+   "options": null,
+   "owner": "Administrator",
+   "parent": null,
+   "parentfield": null,
+   "parenttype": null,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "print_width": null,
+   "read_only": 0,
+   "report_hide": 0,
+   "reqd": 0,
+   "search_index": 0,
+   "translatable": 0,
+   "unique": 0,
+   "width": null
+  }
+ ],
+ "custom_perms": [],
+ "doctype": "Contact",
+ "property_setters": [],
+ "sync_on_migrate": 1
+}
\ No newline at end of file
diff --git a/erpnext/public/js/utils/item_quick_entry.js b/erpnext/public/js/utils/item_quick_entry.js
index 2947d5b..27ef107 100644
--- a/erpnext/public/js/utils/item_quick_entry.js
+++ b/erpnext/public/js/utils/item_quick_entry.js
@@ -8,12 +8,19 @@
 	render_dialog: function() {
 		this.mandatory = this.get_variant_fields().concat(this.mandatory);
 		this.mandatory = this.mandatory.concat(this.get_attributes_fields());
+		this.check_naming_series_based_on();
 		this._super();
 		this.init_post_render_dialog_operations();
 		this.preset_fields_for_template();
 		this.dialog.$wrapper.find('.edit-full').text(__('Edit in full page for more options like assets, serial nos, batches etc.'))
 	},
 
+	check_naming_series_based_on: function() {
+		if (frappe.defaults.get_default("item_naming_by") === "Naming Series") {
+			this.mandatory = this.mandatory.filter(d => d.fieldname !== "item_code");
+		}
+	},
+
 	init_post_render_dialog_operations: function() {
 		this.dialog.fields_dict.attribute_html.$wrapper.append(frappe.render_template("item_quick_entry"));
 		this.init_for_create_variant_trigger();
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 3425f8f..17136e0 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -64,30 +64,40 @@
 		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:
-			filters = {'actual_qty': [">", 0], 'item_code': ['in', items]}
+			bin_filters['actual_qty'] = [">", 0]
 
-			if warehouse:
-				filters['warehouse'] = warehouse
+		# 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'
+		)
 
-			bin_data = frappe._dict(
-				frappe.get_all("Bin", fields = ["item_code", "sum(actual_qty) as actual_qty"],
-				filters = 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:
-			row = {}
+			item_code = item.item_code
+			item_price = item_prices.get(item_code) or {}
+			item_stock_qty = bin_dict.get(item_code)
 
-			row.update(item)
-			item_price = item_prices.get(item.item_code) or {}
-			row.update({
-				'price_list_rate': item_price.get('price_list_rate'),
-				'currency': item_price.get('currency'),
-				'actual_qty': bin_data.get('actual_qty')
-			})
-
-			result.append(row)
+			if display_items_in_stock and not item_stock_qty:
+				pass
+			else:
+				row = {}
+				row.update(item)
+				row.update({
+					'price_list_rate': item_price.get('price_list_rate'),
+					'currency': item_price.get('currency'),
+					'actual_qty': item_stock_qty,
+				})
+				result.append(row)
 
 	res = {
 		'items': result