Merge pull request #9913 from mbauskar/quotation

[minor] fixes for TypeError: get_lead_details() takes at least 1 argument (2 given)
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 4880224..ba4cc83 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -96,7 +96,14 @@
 
 			// expense claim
 			if(jvd.reference_type==="Expense Claim") {
-				return {};
+				return {
+					filters: {
+						'approval_status': 'Approved',
+						'total_sanctioned_amount': ['>', 0],
+						'status': ['!=', 'Paid'],
+						'docstatus': 1
+					}
+				};
 			}
 
 			// journal entry
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index ba38b1f..b24e047 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -160,6 +160,7 @@
 	def set_missing_item_details(self, for_validate=False):
 		"""set missing item values"""
 		from erpnext.stock.get_item_details import get_item_details
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 		if hasattr(self, "items"):
 			parent_dict = {}
@@ -196,7 +197,7 @@
 
 							elif fieldname == "serial_no":
 								stock_qty = item.get("stock_qty") * -1 if item.get("stock_qty") < 0 else item.get("stock_qty")
-								if stock_qty != len(item.get('serial_no').split('\n')):
+								if stock_qty != len(get_serial_nos(item.get('serial_no'))):
 									item.set(fieldname, value)
 
 							elif fieldname == "conversion_factor" and not item.get("conversion_factor"):
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index 48dcfbb..5708c04 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -905,7 +905,7 @@
    "label": "Status", 
    "length": 0, 
    "no_copy": 1, 
-   "options": "Draft\nPaid\nUnpaid\nSubmitted\nCancelled", 
+   "options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled", 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 1, 
@@ -964,7 +964,7 @@
  "istable": 0, 
  "max_attachments": 0, 
  "menu_index": 0, 
- "modified": "2017-06-13 14:29:16.914609", 
+ "modified": "2017-07-17 15:47:23.255142", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Expense Claim", 
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 1cd7257..2781f28 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -39,10 +39,13 @@
 			"2": "Cancelled"
 		}[cstr(self.docstatus or 0)]
 
-		if self.total_sanctioned_amount == self.total_amount_reimbursed and self.docstatus == 1:
+		if self.total_sanctioned_amount > 0 and self.total_sanctioned_amount == self.total_amount_reimbursed \
+			and self.docstatus == 1 and self.approval_status == 'Approved':
 			self.status = "Paid"
-		elif self.docstatus == 1:
+		elif self.total_sanctioned_amount > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
 			self.status = "Unpaid"
+		elif self.docstatus == 1 and self.approval_status == 'Rejected':
+			self.status = 'Rejected'
 
 	def set_payable_account(self):
 		if not self.payable_account and not self.is_paid:
@@ -157,6 +160,9 @@
 		self.total_claimed_amount = 0
 		self.total_sanctioned_amount = 0
 		for d in self.get('expenses'):
+			if self.approval_status == 'Rejected':
+				d.sanctioned_amount = 0.0
+
 			self.total_claimed_amount += flt(d.claim_amount)
 			self.total_sanctioned_amount += flt(d.sanctioned_amount)
 
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_list.js b/erpnext/hr/doctype/expense_claim/expense_claim_list.js
index 7cff8e2..f5a6f4c 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim_list.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim_list.js
@@ -4,8 +4,10 @@
 	get_indicator: function(doc) {
 		if(doc.status == "Paid") {
 			return [__("Paid"), "green", "status,=,'Paid'"];
-		} else {
+		}else if(doc.status == "Unpaid") {
 			return [__("Unpaid"), "orange"];
+		} else if(doc.status == "Rejected") {
+			return [__("Rejected"), "grey"];
 		}
 	}
 };
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 20df7be..e8c24bb 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -113,5 +113,23 @@
 			self.assertEquals(expected_values[gle.account][1], gle.debit)
 			self.assertEquals(expected_values[gle.account][2], gle.credit)
 
+	def test_rejected_expense_claim(self):
+		payable_account = get_payable_account("Wind Power LLC")
+		expense_claim = frappe.get_doc({
+			 "doctype": "Expense Claim",
+			 "employee": "_T-Employee-0001",
+			 "payable_account": payable_account,
+			 "approval_status": "Rejected",
+			 "expenses":
+			 	[{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }]
+		})
+		expense_claim.submit()
+
+		self.assertEquals(expense_claim.status, 'Rejected')
+		self.assertEquals(expense_claim.total_sanctioned_amount, 0.0)
+
+		gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
+		self.assertEquals(len(gl_entry), 0)
+
 def get_payable_account(company):
 	return frappe.db.get_value('Company', company, 'default_payable_account')
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index 8935689..17d0a91 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -69,27 +69,17 @@
 	:param end: End date-time.
 	:param filters: Filters (JSON).
 	"""
-	condition = ''
-	values = {
-		"start_date": getdate(start),
-		"end_date": getdate(end)
-	}
-
 	if filters:
-		if isinstance(filters, basestring):
-			filters = json.loads(filters)
+		filters = json.loads(filters)
+	else:
+		filters = []
 
-		if filters.get('holiday_list'):
-			condition = 'and hlist.name=%(holiday_list)s'
-			values['holiday_list'] = filters['holiday_list']
+	if start:
+		filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
+	if end:
+		filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
 
-	data = frappe.db.sql("""select hlist.name, h.holiday_date, h.description
-		from `tabHoliday List` hlist, tabHoliday h
-		where h.parent = hlist.name
-		and h.holiday_date is not null
-		and h.holiday_date >= %(start_date)s
-		and h.holiday_date <= %(end_date)s
-		{condition}""".format(condition=condition),
-		values, as_dict=True, update={"allDay": 1})
-
-	return data
+	return frappe.get_list('Holiday List',
+		fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description'],
+		filters = filters,
+		update={"allDay": 1})
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 2a282e1..3d2ba87 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -63,13 +63,13 @@
 	def validate_dates(self):
 		if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
 			frappe.throw(_("To date cannot be before from date"))
-			
+
 		if self.half_day and self.half_day_date \
-			and (getdate(self.half_day_date) < getdate(self.from_date) 
+			and (getdate(self.half_day_date) < getdate(self.from_date)
 			or getdate(self.half_day_date) > getdate(self.to_date)):
-				
+
 				frappe.throw(_("Half Day Date should be between From Date and To Date"))
-			
+
 		if not is_lwp(self.leave_type):
 			self.validate_dates_acorss_allocation()
 			self.validate_back_dated_application()
@@ -158,7 +158,7 @@
 			self.name = "New Leave Application"
 
 		for d in frappe.db.sql("""
-			select 
+			select
 				name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
 			from `tabLeave Application`
 			where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
@@ -169,12 +169,12 @@
 				"to_date": self.to_date,
 				"name": self.name
 			}, as_dict = 1):
-			
+
 			if cint(self.half_day)==1 and getdate(self.half_day_date) == getdate(d.half_day_date) and (
-				flt(self.total_leave_days)==0.5 
-				or getdate(self.from_date) == getdate(d.to_date) 
+				flt(self.total_leave_days)==0.5
+				or getdate(self.from_date) == getdate(d.to_date)
 				or getdate(self.to_date) == getdate(d.from_date)):
-				
+
 				total_leaves_on_half_day = self.get_total_leaves_on_half_day()
 				if total_leaves_on_half_day >= 1:
 					self.throw_overlap_error(d)
@@ -199,7 +199,7 @@
 				"half_day_date": self.half_day_date,
 				"name": self.name
 			})[0][0]
-			
+
 		return leave_count_on_half_day_date * 0.5
 
 	def validate_max_days(self):
@@ -400,7 +400,7 @@
 	return lwp and cint(lwp[0][0]) or 0
 
 @frappe.whitelist()
-def get_events(start, end):
+def get_events(start, end, filters=None):
 	events = []
 
 	employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"],
@@ -411,14 +411,14 @@
 		employee=''
 		company=frappe.db.get_value("Global Defaults", None, "default_company")
 
-	from frappe.desk.reportview import build_match_conditions
-	match_conditions = build_match_conditions("Leave Application")
+	from frappe.desk.reportview import get_filters_cond
+	conditions = get_filters_cond("Leave Application")
 
 	# show department leaves for employee
 	if "Employee" in frappe.get_roles():
 		add_department_leaves(events, start, end, employee, company)
 
-	add_leaves(events, start, end, match_conditions)
+	add_leaves(events, start, end, conditions)
 
 	add_block_dates(events, start, end, employee, company)
 	add_holidays(events, start, end, employee, company)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d68f9f2..70d6744 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -419,4 +419,5 @@
 erpnext.patches.v8_1.update_gst_state #17-07-2017
 erpnext.patches.v8_1.removed_report_support_hours
 erpnext.patches.v8_1.add_indexes_in_transaction_doctypes
+erpnext.patches.v8_1.update_expense_claim_status
 erpnext.patches.v8_3.update_company_total_sales
\ No newline at end of file
diff --git a/erpnext/patches/v8_1/update_expense_claim_status.py b/erpnext/patches/v8_1/update_expense_claim_status.py
new file mode 100644
index 0000000..4c1b85a
--- /dev/null
+++ b/erpnext/patches/v8_1/update_expense_claim_status.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doctype('Expense Claim')
+
+	for data in frappe.db.sql(""" select name from `tabExpense Claim`
+		where (docstatus=1 and total_sanctioned_amount=0 and status = 'Paid') or 
+		(docstatus = 1 and approval_status = 'Rejected' and total_sanctioned_amount > 0)""", as_dict=1):
+		doc = frappe.get_doc('Expense Claim', data.name)
+		if doc.approval_status == 'Rejected':
+			for d in doc.expenses:
+				d.db_set("sanctioned_amount", 0, update_modified = False)
+			doc.db_set("total_sanctioned_amount", 0, update_modified = False)
+
+			frappe.db.sql(""" delete from `tabGL Entry` where voucher_type = 'Expense Claim'
+				and voucher_no = %s""", (doc.name))
+
+		doc.set_status()
+		doc.db_set("status", doc.status, update_modified = False)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 8844a48..6416176 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -9,9 +9,8 @@
 import json
 from datetime import timedelta
 from erpnext.controllers.queries import get_match_cond
-from frappe.utils import flt, time_diff_in_hours, get_datetime, getdate, cint, get_datetime_str
+from frappe.utils import flt, time_diff_in_hours, get_datetime, getdate, cint
 from frappe.model.document import Document
-from frappe.model.mapper import get_mapped_doc
 from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours,
 	WorkstationHolidayError)
 from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
@@ -133,7 +132,7 @@
 					if data.name == timesheet.operation_id:
 						summary = self.get_actual_timesheet_summary(timesheet.operation_id)
 						data.time_sheet = time_sheet
-						data.completed_qty = summary.completed_qty 
+						data.completed_qty = summary.completed_qty
 						data.actual_operation_time = summary.mins
 						data.actual_start_time = summary.from_time
 						data.actual_end_time = summary.to_time
@@ -148,7 +147,7 @@
 		"""Returns 'Actual Operating Time'. """
 		return frappe.db.sql("""select
 			sum(tsd.hours*60) as mins, sum(tsd.completed_qty) as completed_qty, min(tsd.from_time) as from_time,
-			max(tsd.to_time) as to_time from `tabTimesheet Detail` as tsd, `tabTimesheet` as ts where 
+			max(tsd.to_time) as to_time from `tabTimesheet Detail` as tsd, `tabTimesheet` as ts where
 			ts.production_order = %s and tsd.operation_id = %s and ts.docstatus=1 and ts.name = tsd.parent""",
 			(self.production_order, operation_id), as_dict=1)[0]
 
@@ -192,7 +191,7 @@
 		if fieldname == 'workstation':
 			cond = "tsd.`{0}`".format(fieldname)
 
-		existing = frappe.db.sql("""select ts.name as name, tsd.from_time as from_time, tsd.to_time as to_time from 
+		existing = frappe.db.sql("""select ts.name as name, tsd.from_time as from_time, tsd.to_time as to_time from
 			`tabTimesheet Detail` tsd, `tabTimesheet` ts where {0}=%(val)s and tsd.parent = ts.name and
 			(
 				(%(from_time)s > tsd.from_time and %(from_time)s < tsd.to_time) or
@@ -211,8 +210,8 @@
 		# check internal overlap
 		for time_log in self.time_logs:
 			if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \
-				args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or 
-				(args.to_time > time_log.from_time and args.to_time < time_log.to_time) or 
+				args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or
+				(args.to_time > time_log.from_time and args.to_time < time_log.to_time) or
 				(args.from_time <= time_log.from_time and args.to_time >= time_log.to_time)):
 				return self
 
@@ -239,7 +238,7 @@
 			self.check_workstation_working_day(data)
 
 	def get_last_working_slot(self, time_sheet, workstation):
-		return frappe.db.sql(""" select max(from_time) as from_time, max(to_time) as to_time 
+		return frappe.db.sql(""" select max(from_time) as from_time, max(to_time) as to_time
 			from `tabTimesheet Detail` where workstation = %(workstation)s""",
 			{'workstation': workstation}, as_dict=True)[0]
 
@@ -277,7 +276,7 @@
 	if parent:
 		cond = "and parent = %(parent)s"
 
-	return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt 
+	return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
 			from `tabTimesheet Detail` where docstatus=1 and project = %(project)s {0} and billable = 1
 			and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1)
 
@@ -290,9 +289,9 @@
 		condition = "and tsd.project = %(project)s"
 
 	return frappe.db.sql("""select distinct tsd.parent from `tabTimesheet Detail` tsd,
-			`tabTimesheet` ts where 
-			ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and 
-			tsd.docstatus = 1 and ts.total_billable_amount > 0 
+			`tabTimesheet` ts where
+			ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and
+			tsd.docstatus = 1 and ts.total_billable_amount > 0
 			and tsd.parent LIKE %(txt)s {condition}
 			order by tsd.parent limit %(start)s, %(page_len)s"""
 			.format(condition=condition), {
@@ -305,7 +304,7 @@
 	if project and project!='':
 		data = get_projectwise_timesheet_data(project, name)
 	else:
-		data = frappe.get_all('Timesheet', 
+		data = frappe.get_all('Timesheet',
 			fields = ["(total_billable_amount - total_billed_amount) as billing_amt", "total_billable_hours as billing_hours"], filters = {'name': name})
 
 	return {
@@ -332,7 +331,7 @@
 @frappe.whitelist()
 def make_salary_slip(source_name, target_doc=None):
 	target = frappe.new_doc("Salary Slip")
-	set_missing_values(source_name, target)	
+	set_missing_values(source_name, target)
 	target.run_method("get_emp_and_leave_details")
 
 	return target
@@ -364,32 +363,21 @@
 	:param filters: Filters (JSON).
 	"""
 	filters = json.loads(filters)
+	from frappe.desk.calendar import get_event_conditions
+	conditions = get_event_conditions("Timesheet", filters)
 
-	conditions = get_conditions(filters)
-	return frappe.db.sql("""select `tabTimesheet Detail`.name as name, 
+	return frappe.db.sql("""select `tabTimesheet Detail`.name as name,
 			`tabTimesheet Detail`.docstatus as status, `tabTimesheet Detail`.parent as parent,
-			from_time as start_date, hours, activity_type, 
-			`tabTimesheet Detail`.project, to_time as end_date, 
-			CONCAT(`tabTimesheet Detail`.parent, ' (', ROUND(hours,2),' hrs)') as title 
-		from `tabTimesheet Detail`, `tabTimesheet` 
-		where `tabTimesheet Detail`.parent = `tabTimesheet`.name 
-			and `tabTimesheet`.docstatus < 2 
+			from_time as start_date, hours, activity_type,
+			`tabTimesheet Detail`.project, to_time as end_date,
+			CONCAT(`tabTimesheet Detail`.parent, ' (', ROUND(hours,2),' hrs)') as title
+		from `tabTimesheet Detail`, `tabTimesheet`
+		where `tabTimesheet Detail`.parent = `tabTimesheet`.name
+			and `tabTimesheet`.docstatus < 2
 			and (from_time <= %(end)s and to_time >= %(start)s) {conditions} {match_cond}
-		""".format(conditions=conditions, match_cond = get_match_cond('Timesheet')), 
+		""".format(conditions=conditions, match_cond = get_match_cond('Timesheet')),
 		{
 			"start": start,
 			"end": end
 		}, as_dict=True, update={"allDay": 0})
 
-def get_conditions(filters):
-	conditions = []
-	for key in filters:
-		if filters.get(key):
-			if frappe.get_meta("Timesheet").has_field(key):
-				dt = 'tabTimesheet'
-			elif frappe.get_meta("Timesheet Detail").has_field(key):
-				dt = 'tabTimesheet Detail'
-				
-			conditions.append("`%s`.%s = '%s'"%(dt, key, filters.get(key)))
-
-	return " and {}".format(" and ".join(conditions)) if conditions else ""
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index c7a2f77..08630e5 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -66,7 +66,7 @@
 				fieldtype:'Float',
 				read_only: 1,
 				label: __(me.has_batch ? 'Total Qty' : 'Qty'),
-				default: me.qty
+				default: 0
 			},
 		];
 
@@ -155,14 +155,10 @@
 	},
 
 	bind_qty: function() {
-		let serial_no_link = this.dialog.fields_dict.serial_no_select;
-		let serial_no_list_field = this.dialog.fields_dict.serial_no;
 		let batches_field = this.dialog.fields_dict.batches;
-
 		let qty_field = this.dialog.fields_dict.qty;
-
-		let update_quantity = (batch) => {
-			if(batch) {
+		if(batches_field) {
+			batches_field.grid.wrapper.on('change', function() {
 				let total_qty = 0;
 				batches_field.grid.wrapper.find(
 					'input[data-fieldname="selected_qty"]').each(function() {
@@ -170,48 +166,6 @@
 					total_qty += Number($(this).val());
 				});
 				qty_field.set_input(total_qty);
-			} else {
-				let serial_numbers = serial_no_list_field.get_value()
-					.replace(/\n/g, ' ').match(/\S+/g) || [];
-				qty_field.set_input(serial_numbers.length);
-			}
-		};
-
-		if(serial_no_link) {
-			let serial_list = [];
-			serial_no_link.$input.on('awesomplete-selectcomplete', function() {
-				if(serial_no_link.get_value().length > 0) {
-					let new_no = serial_no_link.get_value();
-					let list_value = serial_no_list_field.get_value();
-					let new_line = '\n';
-					if(!serial_no_list_field.get_value()) {
-						new_line = '';
-					} else {
-						serial_list = list_value.replace(/\s+/g, ' ').split(' ');
-					}
-					if(!serial_list.includes(new_no)) {
-						serial_no_link.set_new_description('');
-						serial_no_list_field.set_value(list_value + new_line + new_no);
-						update_quantity(0);
-					} else {
-						serial_no_link.set_new_description(new_no + ' is already selected.');
-					}
-				}
-
-				// Should, but doesn't work
-				serial_no_link.set_input('');
-				serial_no_link.$input.blur();
-			});
-
-			serial_no_list_field.$input.on('input', function() {
-				serial_list = serial_no_list_field.get_value().replace(/\s+/g, ' ').split(' ');
-				update_quantity(0);
-			});
-		}
-
-		if(batches_field) {
-			batches_field.grid.wrapper.on('change', function() {
-				update_quantity(1);
 			});
 		}
 	},
@@ -319,6 +273,7 @@
 
 	get_serial_no_fields: function() {
 		var me = this;
+		this.serial_list = [];
 		return [
 			{fieldtype: 'Section Break', label: __('Serial No')},
 			{
@@ -326,10 +281,46 @@
 				label: __('Select'),
 				get_query: function() {
 					return { filters: {item_code: me.item_code}};
+				},
+				onchange: function(e) {
+					if(this.in_local_change) return;
+					this.in_local_change = 1;
+
+					let serial_no_list_field = this.layout.fields_dict.serial_no;
+					let qty_field = this.layout.fields_dict.qty;
+
+					let new_number = this.get_value();
+					let list_value = serial_no_list_field.get_value();
+					let new_line = '\n';
+					if(!list_value) {
+						new_line = '';
+					} else {
+						me.serial_list = list_value.replace(/\n/g, ' ').match(/\S+/g) || [];
+					}
+
+					if(!me.serial_list.includes(new_number)) {
+						this.set_new_description('');
+						serial_no_list_field.set_value(me.serial_list.join('\n') + new_line + new_number);
+						me.serial_list = serial_no_list_field.get_value().replace(/\n/g, ' ').match(/\S+/g) || [];
+					} else {
+						this.set_new_description(new_number + ' is already selected.');
+					}
+
+					qty_field.set_input(me.serial_list.length);
+					this.$input.val("");
+					this.in_local_change = 0;
 				}
 			},
 			{fieldtype: 'Column Break'},
-			{fieldname: 'serial_no', fieldtype: 'Small Text'}
+			{
+				fieldname: 'serial_no',
+				fieldtype: 'Small Text',
+				onchange: function() {
+					me.serial_list = this.get_value()
+						.replace(/\n/g, ' ').match(/\S+/g) || [];
+					this.layout.fields_dict.qty.set_input(me.serial_list.length);
+				}
+			}
 		];
 	}
 });
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 5e373d6..c3e3cc5 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -13,11 +13,18 @@
 
 	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)
 
 	data = []
 	for (company, item, warehouse) in sorted(iwb_map):
 		qty_dict = iwb_map[(company, item, warehouse)]
+		item_reorder_level = 0
+		item_reorder_qty = 0
+		if item + warehouse in item_reorder_detail_map:
+			item_reorder_level = item_reorder_detail_map[item + warehouse]["warehouse_reorder_level"]
+			item_reorder_qty = item_reorder_detail_map[item + warehouse]["warehouse_reorder_qty"]
+			
 		data.append([item, item_map[item]["item_name"],
 			item_map[item]["item_group"],
 			item_map[item]["brand"],
@@ -27,6 +34,8 @@
 			qty_dict.in_val, qty_dict.out_qty,
 			qty_dict.out_val, qty_dict.bal_qty,
 			qty_dict.bal_val, qty_dict.val_rate,
+			item_reorder_level,
+			item_reorder_qty,
 			company
 		])
 
@@ -52,6 +61,8 @@
 		_("Balance Qty")+":Float:100",
 		_("Balance Value")+":Float:100",
 		_("Valuation Rate")+":Float:90",
+		_("Reorder Level")+":Float:80",
+		_("Reorder Qty")+":Float:80",
 		_("Company")+":Link/Company:100"
 	]
 
@@ -180,7 +191,19 @@
 	items = frappe.db.sql("""select name, item_name, stock_uom, item_group, brand, description
 		from tabItem {condition}""".format(condition=condition), value, as_dict=1)
 
-	return dict((d.name, d) for d in items)
+	return dict((d.name , d) for d in items)
+
+def get_item_reorder_details(filters):
+	condition = ''
+	value = ()
+	if filters.get("item_code"):
+		condition = "where parent=%s"
+		value = (filters.get("item_code"),)
+
+	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)
+
+	return dict((d.parent + d.warehouse, d) for d in item_reorder_details)
 
 def validate_filters(filters):
 	if not (filters.get("item_code") or filters.get("warehouse")):