Merge branch 'master' into develop
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index eb7f91d..641b9b6 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -2,7 +2,7 @@
 from __future__ import unicode_literals
 import frappe
 
-__version__ = '8.0.40'
+__version__ = '8.0.41'
 
 
 def get_default_company(user=None):
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 3fbde29..0e4edcc 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -299,6 +299,7 @@
 			this.calculate_write_off_amount()
 		}else {
 			this.frm.set_value("change_amount", 0.0)
+			this.frm.set_value("base_change_amount", 0.0)
 		}
 
 		this.frm.refresh_fields();
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index cede8f4..42327b9 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -19,10 +19,10 @@
 		["Converted", "has_customer"],
 	],
 	"Opportunity": [
-		["Quotation", "has_active_quotation"],
-		["Converted", "has_ordered_quotation"],
 		["Lost", "eval:self.status=='Lost'"],
 		["Lost", "has_lost_quotation"],
+		["Quotation", "has_active_quotation"],
+		["Converted", "has_ordered_quotation"],
 		["Closed", "eval:self.status=='Closed'"]
 	],
 	"Quotation": [
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 822d50b..50f6b61 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -440,16 +440,16 @@
 				self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance
 					- flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
 
-		if self.doc.doctype == "Sales Invoice":
+		if self.doc.doctype == "Sales Invoice":			
 			self.doc.round_floats_in(self.doc, ["paid_amount"])
-			paid_amount = self.doc.paid_amount \
-				if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
-
-			change_amount = self.doc.change_amount \
-				if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
-
 			self.calculate_write_off_amount()
 			self.calculate_change_amount()
+			
+			paid_amount = self.doc.paid_amount \
+				if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
+			
+			change_amount = self.doc.change_amount \
+				if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
 
 			self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) +
 				flt(change_amount), self.doc.precision("outstanding_amount"))
@@ -475,7 +475,9 @@
 	def calculate_change_amount(self):
 		self.doc.change_amount = 0.0
 		self.doc.base_change_amount = 0.0
-		if self.doc.paid_amount > self.doc.grand_total:
+		if self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
+			and any([d.type == "Cash" for d in self.doc.payments]):
+
 			self.doc.change_amount = flt(self.doc.paid_amount - self.doc.grand_total +
 				self.doc.write_off_amount, self.doc.precision("change_amount"))
 
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 974b23e..0e14c85 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -35,6 +35,7 @@
 		var doc = frm.doc;
 		frm.events.enquiry_from(frm);
 		frm.trigger('set_contact_link');
+		erpnext.toggle_naming_series();
 
 		if(!doc.__islocal && doc.status!=="Lost") {
 			if(doc.with_items){
@@ -53,6 +54,20 @@
 				frm.add_custom_button(__('Lost'),
 					cur_frm.cscript['Declare Opportunity Lost']);
 			}
+		},
+
+		if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {
+			if(frm.doc.status==="Open") {
+				frm.add_custom_button(__("Close"), function() {
+					frm.set_value("status", "Closed");
+					frm.save();
+				});
+			} else {
+				frm.add_custom_button(__("Reopen"), function() {
+					frm.set_value("status", "Open");
+					frm.save();
+				});
+			}
 		}
 	},
 
@@ -122,25 +137,6 @@
 
 $.extend(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm}));
 
-cur_frm.cscript.refresh = function(doc, cdt, cdn) {
-	erpnext.toggle_naming_series();
-
-	var frm = cur_frm;
-	if(!doc.__islocal && frm.perm[0].write && doc.docstatus==0) {
-		if(frm.doc.status==="Open") {
-			frm.add_custom_button(__("Close"), function() {
-				frm.set_value("status", "Closed");
-				frm.save();
-			});
-		} else {
-			frm.add_custom_button(__("Reopen"), function() {
-				frm.set_value("status", "Open");
-				frm.save();
-			});
-		}
-	}
-}
-
 cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) {
 	if(doc.enquiry_from == 'Lead' && doc.lead)
 		cur_frm.cscript.lead(doc, cdt, cdn);
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index eebf464..58f26fe 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -31,7 +31,6 @@
 		if not self.enquiry_from:
 			frappe.throw(_("Opportunity From field is mandatory"))
 
-		self.set_status()
 		self.validate_item_details()
 		self.validate_uom_is_integer("uom", "qty")
 		self.validate_lead_cust()
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index af4dd3b..d64409d 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -178,7 +178,8 @@
 		"erpnext.hr.doctype.employee.employee.send_birthday_reminders",
 		"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
 		"erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries",
-		'erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary'
+		"erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary",
+		"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status"
 	]
 }
 
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 01dc341..1cd7257 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -11,6 +11,7 @@
 from erpnext.accounts.general_ledger import make_gl_entries
 from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
 from erpnext.controllers.accounts_controller import AccountsController
+from frappe.utils.csvutils import getlink
 
 class InvalidExpenseApproverError(frappe.ValidationError): pass
 
@@ -146,7 +147,7 @@
 			frappe.throw(_("Cost center is required to book an expense claim"))
 
 		if not self.payable_account:
-			frappe.throw(_("Please set default payable account in the employee {0}").format(self.employee))
+			frappe.throw(_("Please set default payable account for the company {0}").format(getlink("Company",self.company)))
 
 		if self.is_paid:
 			if not self.mode_of_payment:
diff --git a/erpnext/hr/doctype/training_event/training_event.json b/erpnext/hr/doctype/training_event/training_event.json
index 1f79d23..03b58b4 100644
--- a/erpnext/hr/doctype/training_event/training_event.json
+++ b/erpnext/hr/doctype/training_event/training_event.json
@@ -1,5 +1,6 @@
 {
  "allow_copy": 0, 
+ "allow_guest_to_view": 0, 
  "allow_import": 1, 
  "allow_rename": 1, 
  "autoname": "field:event_name", 
@@ -12,6 +13,7 @@
  "editable_grid": 1, 
  "fields": [
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -27,7 +29,7 @@
    "in_standard_filter": 0, 
    "label": "Event Name", 
    "length": 0, 
-   "no_copy": 0, 
+   "no_copy": 1, 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
@@ -41,6 +43,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 1, 
    "bold": 0, 
    "collapsible": 0, 
@@ -71,6 +74,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -99,6 +103,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -129,6 +134,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -157,6 +163,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -186,6 +193,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -216,6 +224,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -244,6 +253,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -274,6 +284,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -303,6 +314,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -331,6 +343,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -361,6 +374,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -390,6 +404,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -418,6 +433,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -447,6 +463,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -476,6 +493,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -504,6 +522,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -533,6 +552,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -562,6 +582,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -592,6 +613,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 1, 
    "bold": 0, 
    "collapsible": 0, 
@@ -622,6 +644,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -651,17 +674,17 @@
    "unique": 0
   }
  ], 
+ "has_web_view": 0, 
  "hide_heading": 0, 
  "hide_toolbar": 0, 
  "idx": 0, 
  "image_view": 0, 
  "in_create": 0, 
- "in_dialog": 0, 
  "is_submittable": 1, 
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2017-02-17 16:51:35.141403", 
+ "modified": "2017-05-29 06:13:38.411039", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Training Event", 
diff --git a/erpnext/hr/doctype/training_result/training_result.py b/erpnext/hr/doctype/training_result/training_result.py
index 16b76a7..36c3cb9 100644
--- a/erpnext/hr/doctype/training_result/training_result.py
+++ b/erpnext/hr/doctype/training_result/training_result.py
@@ -13,7 +13,9 @@
 	
 	def send_result(self):
 		for emp in self.employees:
-			message = "Thank You for attending {0}. You grade is {1}".format(self.training_event, emp.grade)
+			message = "Thank You for attending {0}.".format(self.training_event)
+			if emp.grade: 
+				message = message + "Your grade: {0}".format(emp.grade)
 			frappe.sendmail(frappe.db.get_value("Employee", emp.employee, "company_email"), \
 				subject=_("{0} Results".format(self.training_event)), \
 				content=message)
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py
index ff1082d..68da336 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/production_order.py
@@ -20,6 +20,7 @@
 from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
 from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
 from erpnext.stock.utils import get_bin
+from frappe.utils.csvutils import getlink
 
 class OverProductionError(frappe.ValidationError): pass
 class StockOverProductionError(frappe.ValidationError): pass
@@ -311,7 +312,7 @@
 
 		if timesheet and timesheet.get("time_logs"):
 			timesheet.save()
-			timesheets.append(timesheet.name)
+			timesheets.append(getlink("Timesheet", timesheet.name))
 
 		self.planned_end_date = self.operations[-1].planned_end_time
 		if timesheets:
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 154f0d0..8844a48 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -368,7 +368,8 @@
 	conditions = get_conditions(filters)
 	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, project, to_time as end_date, 
+			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 
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 849275f..dc1db35 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -604,11 +604,18 @@
 
 	calculate_change_amount: function(){
 		this.frm.doc.change_amount = 0.0;
-		if(this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return){
-			this.frm.doc.change_amount = flt(this.frm.doc.paid_amount - this.frm.doc.grand_total +
-				this.frm.doc.write_off_amount, precision("change_amount"));
-			this.frm.doc.base_change_amount = flt(this.frm.doc.base_paid_amount - this.frm.doc.base_grand_total +
-				this.frm.doc.base_write_off_amount, precision("base_change_amount"));
+		this.frm.doc.base_change_amount = 0.0;
+		if(this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) {
+			var payment_types = $.map(cur_frm.doc.payments, function(d) { return d.type });
+			if (in_list(payment_types, 'Cash')) {
+				this.frm.doc.change_amount = flt(this.frm.doc.paid_amount - this.frm.doc.grand_total +
+					this.frm.doc.write_off_amount, precision("change_amount"));
+					
+				this.frm.doc.base_change_amount = flt(this.frm.doc.base_paid_amount - 
+					this.frm.doc.base_grand_total + this.frm.doc.base_write_off_amount, 
+					precision("base_change_amount"));
+				
+			}
 		}
 	},
 
diff --git a/erpnext/schools/doctype/student_group/student_group.py b/erpnext/schools/doctype/student_group/student_group.py
index e861421..9cdf9c7 100644
--- a/erpnext/schools/doctype/student_group/student_group.py
+++ b/erpnext/schools/doctype/student_group/student_group.py
@@ -14,7 +14,7 @@
 		self.validate_strength()
 		if frappe.defaults.get_defaults().student_validation_setting: 
 			self.validate_students()
-		self.validate_roll_no()
+		self.validate_and_set_child_table_fields()
 		validate_duplicate_student(self.students)
 
 	def validate_mandatory_fields(self):
@@ -38,9 +38,16 @@
 			if not frappe.db.get_value("Student", d.student, "enabled") and d.active:
 				frappe.throw(_("{0} - {1} is inactive student".format(d.group_roll_number, d.student_name)))
 
-	def validate_roll_no(self):
+	def validate_and_set_child_table_fields(self):
+		roll_numbers = [d.group_roll_number for d in self.students if d.group_roll_number]
+		max_roll_no = max(roll_numbers) if roll_numbers else 0
 		roll_no_list = []
 		for d in self.students:
+			if not d.student_name:
+				d.student_name = frappe.db.get_value("Student", d.student, "title")
+			if not d.group_roll_number:
+				max_roll_no += 1
+				d.group_roll_number = max_roll_no
 			if d.group_roll_number in roll_no_list:
 				frappe.throw(_("Duplicate roll number for student {0}".format(d.student_name)))
 			else:
diff --git a/erpnext/schools/doctype/student_group/test_student_group.py b/erpnext/schools/doctype/student_group/test_student_group.py
index 6b599aa..18a6b14 100644
--- a/erpnext/schools/doctype/student_group/test_student_group.py
+++ b/erpnext/schools/doctype/student_group/test_student_group.py
@@ -5,8 +5,23 @@
 
 import frappe
 import unittest
-
-# test_records = frappe.get_test_records('Student Group')
+from frappe.utils.make_random import get_random
 
 class TestStudentGroup(unittest.TestCase):
-	pass
+	 def test_student_roll_no(self):
+	 	doc = frappe.get_doc({
+	 		"doctype": "Student Group",
+	 		"student_group_name": "_Test Student Group R",
+			"group_based_on": "Activity"
+	 		}).insert()
+
+	 	student_list = []
+	 	while len(student_list) < 3:
+	 		s = get_random("Student")
+	 		if s not in student_list:
+	 			student_list.append(s)
+
+	 	doc.extend("students", [{"student":d} for d in student_list])
+	 	doc.save()
+	 	self.assertEquals(max([d.group_roll_number for d in doc.students]), 3)
+
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index bc9b893..03d4d73 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -324,3 +324,12 @@
 						update_rejected_serial_nos = False
 						if accepted_serial_nos_updated:
 							break
+
+def update_maintenance_status():
+	serial_nos = frappe.db.sql('''select name from `tabSerial No` where (amc_expiry_date<%s or
+		warranty_expiry_date<%s) and maintenance_status not in ('Out of Warranty', 'Out of AMC')''',
+		(nowdate(), nowdate()))
+	for serial_no in serial_nos:
+		doc = frappe.get_doc("Serial No", serial_no[0])
+		doc.set_maintenance_status()
+		frappe.db.set_value('Serial No', doc.name, 'maintenance_status', doc.maintenance_status)