Merge pull request #8975 from almeidapaulopt/milestones

Milestones in Project Gantt
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index eb69d19..eb7f91d 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -2,7 +2,7 @@
 from __future__ import unicode_literals
 import frappe
 
-__version__ = '8.0.34'
+__version__ = '8.0.40'
 
 
 def get_default_company(user=None):
diff --git a/erpnext/accounts/doctype/asset/asset.py b/erpnext/accounts/doctype/asset/asset.py
index 070461e..986beb7 100644
--- a/erpnext/accounts/doctype/asset/asset.py
+++ b/erpnext/accounts/doctype/asset/asset.py
@@ -19,7 +19,8 @@
 		self.validate_asset_values()
 		self.make_depreciation_schedule()
 		self.set_accumulated_depreciation()
-		self.validate_expected_value_after_useful_life()
+		if self.get("schedules"):
+			self.validate_expected_value_after_useful_life()
 		# Validate depreciation related accounts
 		get_depreciation_accounts(self)
 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 007afe4..3fbde29 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -323,7 +323,8 @@
 		}
 	}
 
-	item_fields_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse']
+	item_fields_stock = ['batch_no', 'actual_batch_qty', 'actual_qty', 'expense_account',
+		'warehouse', 'expense_account', 'quality_inspection']
 	cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
 		(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 6e3990a..6d88326 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -88,6 +88,8 @@
 		self.validate_c_form()
 		self.validate_time_sheets_are_submitted()
 		self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
+		if not self.is_return:
+			self.validate_serial_numbers()
 		self.update_packing_list()
 		self.set_billing_hours_and_amount()
 		self.update_timesheet_billing_for_project()
@@ -125,6 +127,7 @@
 		if not self.is_return:
 			self.update_billing_status_for_zero_amount_refdoc("Sales Order")
 			self.check_credit_limit()
+			self.update_serial_no()
 
 		if not cint(self.is_pos) == 1 and not self.is_return:
 			self.update_against_document_in_jv()
@@ -155,6 +158,7 @@
 
 		if not self.is_return:
 			self.update_billing_status_for_zero_amount_refdoc("Sales Order")
+			self.update_serial_no(in_cancel=True)
 
 		self.validate_c_form_on_cancel()
 
@@ -411,10 +415,10 @@
 				throw(_("Customer {0} does not belong to project {1}").format(self.customer,self.project))
 
 	def validate_pos(self):
-		if flt(self.paid_amount) + flt(self.write_off_amount) \
-				- flt(self.grand_total) > 1/(10**(self.precision("grand_total") + 1)) and self.is_return:
-			frappe.throw(_("""Paid amount + Write Off Amount can not be greater than Grand Total"""))
-
+		if self.is_return:
+			if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) < \
+				1/(10**(self.precision("grand_total") + 1)):
+					frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
 
 	def validate_item_code(self):
 		for d in self.get('items'):
@@ -781,6 +785,61 @@
 
 		self.due_date = None
 
+	def update_serial_no(self, in_cancel=False):
+		""" update Sales Invoice refrence in Serial No """
+
+		for item in self.items:
+			if not item.serial_no:
+				continue
+
+			serial_nos = ["'%s'"%serial_no for serial_no in item.serial_no.split("\n")]
+
+			frappe.db.sql(""" update `tabSerial No` set sales_invoice='{invoice}'
+				where name in ({serial_nos})""".format(
+					invoice='' if in_cancel else self.name,
+					serial_nos=",".join(serial_nos)
+				)
+			)
+
+	def validate_serial_numbers(self):
+		"""
+			validate serial number agains Delivery Note and Sales Invoice
+		"""
+		self.validate_serial_against_delivery_note()
+		self.validate_serial_against_sales_invoice()
+
+	def validate_serial_against_delivery_note(self):
+		""" 
+			validate if the serial numbers in Sales Invoice Items are same as in
+			Delivery Note Item
+		"""
+
+		for item in self.items:
+			if not item.delivery_note or not item.dn_detail:
+				continue
+
+			serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
+			dn_serial_nos = set(serial_nos.split("\n"))
+
+			serial_nos = item.serial_no or ""
+			si_serial_nos = set(serial_nos.split("\n"))
+
+			if si_serial_nos - dn_serial_nos:
+				frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx)))
+
+	def validate_serial_against_sales_invoice(self):
+		""" check if serial number is already used in other sales invoice """
+		for item in self.items:
+			if not item.serial_no:
+				continue
+
+			for serial_no in item.serial_no.split("\n"):
+				sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice")
+				if sales_invoice and self.name != sales_invoice:
+					frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}".format(
+						serial_no, sales_invoice
+					)))
+
 def get_list_context(context=None):
 	from erpnext.controllers.website_list_for_contact import get_list_context
 	list_context = get_list_context(context)
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 0ada847..8335879 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -737,6 +737,12 @@
 		self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
 		self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0],
 			"delivery_document_no"), si.name)
+		self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"),
+			si.name)
+
+		# check if the serial number is already linked with any other Sales Invoice
+		_si = frappe.copy_doc(si.as_dict())
+		self.assertRaises(frappe.ValidationError, _si.insert)
 
 		return si
 
@@ -750,6 +756,7 @@
 		self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
 		self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0],
 			"delivery_document_no"))
+		self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"))
 
 	def test_serialize_status(self):
 		serial_no = frappe.get_doc({
@@ -768,6 +775,27 @@
 
 		self.assertRaises(SerialNoWarehouseError, si.submit)
 
+	def test_serial_numbers_against_delivery_note(self):
+		""" 
+			check if the sales invoice item serial numbers and the delivery note items
+			serial numbers are same
+		"""
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		se = make_serialized_item()
+		serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+		dn = create_delivery_note(item=se.get("items")[0].item_code, serial_no=serial_nos[0])
+		dn.submit()
+
+		si = make_sales_invoice(dn.name)
+		si.save()
+
+		self.assertEquals(si.get("items")[0].serial_no, dn.get("items")[0].serial_no)
+
 	def test_invoice_due_date_against_customers_credit_days(self):
 		# set customer's credit days
 		frappe.db.set_value("Customer", "_Test Customer", "credit_days_based_on", "Fixed Days")
diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js
index e36fa29..dc524e1 100644
--- a/erpnext/accounts/page/pos/pos.js
+++ b/erpnext/accounts/page/pos/pos.js
@@ -355,8 +355,14 @@
 
 		this.print_template_data = frappe.render_template("print_template", {
 			content: this.print_template,
-			title: "POS", base_url: frappe.urllib.get_base_url(), print_css: frappe.boot.print_css,
-			print_settings: this.print_settings, header: this.letter_head.header, footer: this.letter_head.footer
+			title: "POS",
+			base_url: frappe.urllib.get_base_url(),
+			print_css: frappe.boot.print_css,
+			print_settings: this.print_settings,
+			header: this.letter_head.header,
+			footer: this.letter_head.footer,
+			landscape: false,
+			columns: []
 		})
 	},
 
@@ -703,7 +709,7 @@
 				filter: function (item, input) {
 					var value = item.value.toLowerCase();
 					if (value.indexOf('is_action') !== -1 ||
-						value.indexOf(input) !== -1) {
+						value.indexOf(input.toLowerCase()) !== -1) {
 						return true;
 					}
 				},
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index f0c29bc..a6ca3b6 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -7,10 +7,13 @@
 import datetime
 from frappe import _, msgprint, scrub
 from frappe.defaults import get_user_permissions
-from frappe.utils import add_days, getdate, formatdate, get_first_day, date_diff, add_years, get_timestamp
+from frappe.utils import add_days, getdate, formatdate, get_first_day, date_diff, \
+	add_years, get_timestamp, nowdate, flt
 from frappe.geo.doctype.address.address import get_address_display, get_default_address
 from frappe.email.doctype.contact.contact import get_contact_details, get_default_contact
 from erpnext.exceptions import PartyFrozen, InvalidCurrency, PartyDisabled, InvalidAccountCurrency
+from erpnext.accounts.utils import get_fiscal_year
+from erpnext import get_default_currency
 
 class DuplicatePartyAccountError(frappe.ValidationError): pass
 
@@ -359,4 +362,39 @@
 		timestamp = get_timestamp(date)
 		out.update({ timestamp: count })
 
-	return out
\ No newline at end of file
+	return out
+	
+def get_dashboard_info(party_type, party):
+	current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True)
+	company = frappe.db.get_default("company") or frappe.get_all("Company")[0].name
+	party_account_currency = get_party_account_currency(party_type, party, company)
+	company_default_currency = get_default_currency() \
+		or frappe.db.get_value('Company', company, 'default_currency')
+		
+	if party_account_currency==company_default_currency:
+		total_field = "base_grand_total"
+	else:
+		total_field = "grand_total"
+		
+	doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice"
+	
+	billing_this_year = frappe.db.sql("""
+		select sum({0})
+		from `tab{1}`
+		where {2}=%s and docstatus=1 and posting_date between %s and %s
+	""".format(total_field, doctype, party_type.lower()), 
+	(party, current_fiscal_year.year_start_date, current_fiscal_year.year_end_date))
+		
+	total_unpaid = frappe.db.sql("""
+		select sum(debit_in_account_currency) - sum(credit_in_account_currency)
+		from `tabGL Entry`
+		where party_type = %s and party=%s""", (party_type, party))
+
+	info = {}
+	info["billing_this_year"] = flt(billing_this_year[0][0]) if billing_this_year else 0
+	info["currency"] = party_account_currency
+	info["total_unpaid"] = flt(total_unpaid[0][0]) if total_unpaid else 0
+	if party_type == "Supplier":
+		info["total_unpaid"] = -1 * info["total_unpaid"]
+	
+	return info
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 704e828..3b6ae62 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -6,11 +6,9 @@
 import frappe.defaults
 from frappe import msgprint, _
 from frappe.model.naming import make_autoname
-from frappe.geo.address_and_contact import (load_address_and_contact,
-	delete_contact_and_address)
-
+from frappe.geo.address_and_contact import load_address_and_contact, delete_contact_and_address
 from erpnext.utilities.transaction_base import TransactionBase
-from erpnext.accounts.party import validate_party_accounts, get_timeline_data # keep this
+from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
 
 class Supplier(TransactionBase):
 	def get_feed(self):
@@ -22,22 +20,7 @@
 		self.load_dashboard_info()
 
 	def load_dashboard_info(self):
-		billing_this_year = frappe.db.sql("""
-			select sum(credit_in_account_currency) - sum(debit_in_account_currency)
-			from `tabGL Entry`
-			where voucher_type='Purchase Invoice' and party_type = 'Supplier'
-				and party=%s and fiscal_year = %s""",
-			(self.name, frappe.db.get_default("fiscal_year")))
-
-		total_unpaid = frappe.db.sql("""select sum(outstanding_amount)
-			from `tabPurchase Invoice`
-			where supplier=%s and docstatus = 1""", self.name)
-
-
-		info = {}
-		info["billing_this_year"] = billing_this_year[0][0] if billing_this_year else 0
-		info["total_unpaid"] = total_unpaid[0][0] if total_unpaid else 0
-
+		info = get_dashboard_info(self.doctype, self.name)
 		self.set_onload('dashboard_info', info)
 
 	def autoname(self):
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index c1c1fd7..822d50b 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -462,7 +462,8 @@
 
 		if self.doc.is_pos:
 			for payment in self.doc.get('payments'):
-				payment.base_amount = flt(payment.amount * self.doc.conversion_rate)
+				payment.amount = flt(payment.amount)
+				payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
 				paid_amount += payment.amount
 				base_paid_amount += payment.base_amount
 		elif not self.doc.is_return:
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index a37caea..aafc128 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -43,7 +43,8 @@
 			if self.email_id == self.contact_by:
 				frappe.throw(_("Next Contact By cannot be same as the Lead Email Address"))
 
-			self.image = has_gravatar(self.email_id)
+			if self.is_new() or not self.image:
+				self.image = has_gravatar(self.email_id)
 
 		if self.contact_date and getdate(self.contact_date) < getdate(nowdate()):
 			frappe.throw(_("Next Contact Date cannot be in the past"))
diff --git a/erpnext/docs/user/manual/en/stock/item/item-valuation-fifo-and-moving-average.md b/erpnext/docs/user/manual/en/stock/item/item-valuation-fifo-and-moving-average.md
index 9344870..fc5ef00 100644
--- a/erpnext/docs/user/manual/en/stock/item/item-valuation-fifo-and-moving-average.md
+++ b/erpnext/docs/user/manual/en/stock/item/item-valuation-fifo-and-moving-average.md
@@ -24,5 +24,3 @@
   * **Moving Average:** In this method, ERPNext assumes that the value of the item at any point is the average price of the units of that Item in stock. For example, if the value of an Item is X in a Warehouse with quantity Y and another quantity Y1 is added to the Warehouse at cost X1, the new value X2 would be:
 
 > New Value X2 = (X * Y + X1 * Y1) / (Y + Y1)
-
-{next}
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index acc1c64..2de20dd 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -27,7 +27,8 @@
 
 	def validate(self):
 		self.check_email_id_is_unique()
-		validate_email_add(self.email_id, True)
+		if self.email_id:
+			validate_email_add(self.email_id, True)
 
 		if not self.applicant_name and self.email_id:
 			guess = self.email_id.split('@')[0]
diff --git a/erpnext/hr/doctype/offer_letter/offer_letter.js b/erpnext/hr/doctype/offer_letter/offer_letter.js
index 643eaa8..125a425 100755
--- a/erpnext/hr/doctype/offer_letter/offer_letter.js
+++ b/erpnext/hr/doctype/offer_letter/offer_letter.js
@@ -5,8 +5,10 @@
 
 frappe.ui.form.on("Offer Letter", {
 	select_terms: function(frm) {
-		frappe.model.get_value("Terms and Conditions", frm.doc.select_terms, "terms", function(value) {
-			frm.set_value("terms", value.terms);
+		erpnext.utils.get_terms(frm.doc.select_terms, frm.doc, function(r) {
+			if(!r.exc) {
+				me.frm.set_value("terms", r.message);
+			}
 		});
 	},
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index c42403c..7dc6f2c 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -1586,7 +1586,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2017-04-10 12:13:59.630780", 
+ "modified": "2017-05-20 13:10:59.630780", 
  "modified_by": "Administrator", 
  "module": "Manufacturing", 
  "name": "BOM", 
@@ -1595,26 +1595,6 @@
   {
    "amend": 0, 
    "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "All", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
@@ -1662,4 +1642,4 @@
  "sort_order": "DESC", 
  "track_changes": 1, 
  "track_seen": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ac91f60..fef0872 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -396,3 +396,5 @@
 erpnext.patches.v8_0.rename_total_margin_to_rate_with_margin # 11-05-2017
 erpnext.patches.v8_0.fix_status_for_invoices_with_negative_outstanding
 erpnext.patches.v8_0.make_payments_table_blank_for_non_pos_invoice
+erpnext.patches.v8_0.set_sales_invoice_serial_number_from_delivery_note
+erpnext.patches.v8_0.delete_schools_depricated_doctypes
diff --git a/erpnext/patches/v7_2/update_assessment_modules.py b/erpnext/patches/v7_2/update_assessment_modules.py
index 9c00902..9075bbf 100644
--- a/erpnext/patches/v7_2/update_assessment_modules.py
+++ b/erpnext/patches/v7_2/update_assessment_modules.py
@@ -3,26 +3,32 @@
 
 def execute():
 	#Rename Grading Structure to Grading Scale
-	frappe.rename_doc("DocType", "Grading Structure", "Grading Scale", force=True)
-	frappe.rename_doc("DocType", "Grade Interval", "Grading Scale Interval", force=True)
+	if not frappe.db.exists("DocType", "Grading Scale"):
+		frappe.rename_doc("DocType", "Grading Structure", "Grading Scale", force=True)
+	if not frappe.db.exists("DocType", "Grading Scale Interval"):
+		frappe.rename_doc("DocType", "Grade Interval", "Grading Scale Interval", force=True)
 
 	frappe.reload_doc("schools", "doctype", "grading_scale_interval")
-	rename_field("Grading Scale Interval", "to_score", "threshold")
+	if "to_score" in frappe.db.get_table_columns("Grading Scale Interval"):
+		rename_field("Grading Scale Interval", "to_score", "threshold")
 
-	frappe.rename_doc("DocType", "Assessment", "Assessment Plan", force=True)
+	if not frappe.db.exists("DocType", "Assessment Plan"):
+		frappe.rename_doc("DocType", "Assessment", "Assessment Plan", force=True)
 
 	#Rename Assessment Results
 	frappe.reload_doc("schools", "doctype", "assessment_plan")
-	rename_field("Assessment Plan", "grading_structure", "grading_scale")
+	if "grading_structure" in frappe.db.get_table_columns("Assessment Plan"):
+		rename_field("Assessment Plan", "grading_structure", "grading_scale")
 
 	frappe.reload_doc("schools", "doctype", "assessment_result")
 	frappe.reload_doc("schools", "doctype", "assessment_result_detail")
 	frappe.reload_doc("schools", "doctype", "assessment_criteria")
 
 
-	for assessment in frappe.get_all("Assessment Plan", fields=["name", "grading_scale"], filters = [["docstatus", "!=", 2 ]]):
-		print assessment
-		for stud_result in frappe.db.sql("select * from `tabAssessment Result` where parent= %s", assessment.name, as_dict=True):
+	for assessment in frappe.get_all("Assessment Plan", 
+			fields=["name", "grading_scale"], filters = [["docstatus", "!=", 2 ]]):
+		for stud_result in frappe.db.sql("select * from `tabAssessment Result` where parent= %s", 
+				assessment.name, as_dict=True):
 			if stud_result.result:
 				assessment_result = frappe.new_doc("Assessment Result")
 				assessment_result.student = stud_result.student
diff --git a/erpnext/patches/v8_0/delete_schools_depricated_doctypes.py b/erpnext/patches/v8_0/delete_schools_depricated_doctypes.py
new file mode 100644
index 0000000..09a78ed
--- /dev/null
+++ b/erpnext/patches/v8_0/delete_schools_depricated_doctypes.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2017, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	""" delete doctypes """
+
+	if frappe.db.exists("DocType", "Grading Structure"):
+		frappe.delete_doc("DocType", "Grading Structure", force=1)
+
+	if frappe.db.exists("DocType", "Grade Interval"):
+		frappe.delete_doc("DocType", "Grade Interval", force=1)
\ No newline at end of file
diff --git a/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py b/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py
new file mode 100644
index 0000000..2ae74cd
--- /dev/null
+++ b/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
+
+def execute():
+	""" Set the Serial Numbers in Sales Invoice Item from Delivery Note Item """
+
+	frappe.reload_doc("stock", "doctype", "serial_no")
+
+	frappe.db.sql(""" update `tabSales Invoice Item` sii inner join 
+		`tabDelivery Note Item` dni on sii.dn_detail=dni.name and  sii.qty=dni.qty
+		set sii.serial_no=dni.serial_no where sii.parent IN (select si.name 
+			from `tabSales Invoice` si where si.update_stock=0 and si.docstatus=1)""")
+
+	items = frappe.db.sql(""" select  sii.parent, sii.serial_no from  `tabSales Invoice Item` sii
+		left join `tabSales Invoice` si on sii.parent=si.name
+		where si.docstatus=1 and si.update_stock=0""", as_dict=True)
+
+	for item in items:
+		sales_invoice = item.get("parent", None)
+		serial_nos = item.get("serial_no", "")
+
+		if not sales_invoice or not serial_nos:
+			continue
+
+		serial_nos = ["'%s'"%no for no in serial_nos.split("\n")]
+
+		frappe.db.sql("""
+			UPDATE 
+				`tabSerial No`
+			SET 
+				sales_invoice='{sales_invoice}'
+			WHERE
+				name in ({serial_nos})
+			""".format(
+				sales_invoice=sales_invoice,
+				serial_nos=",".join(serial_nos)
+			)
+		)
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 44bf21d..6357a52 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -15,10 +15,10 @@
 					// if rate is greater than price_list_rate, set margin
 					// or set discount
 					item.discount_percentage = 0;
-					item.margin_type = 'Percentage';
-					item.margin_rate_or_amount = flt(Math.abs(1 - item.rate / item.price_list_rate) * 100.0, 
-						precision("discount_percentage", item));
-					item.rate_with_margin = item.rate; 
+					item.margin_type = 'Amount';
+					item.margin_rate_or_amount = flt(item.rate - item.price_list_rate, 
+						precision("margin_rate_or_amount", item));
+					item.rate_with_margin = item.rate;
 				} else {
 					item.discount_percentage = flt((1 - item.rate / item.price_list_rate) * 100.0, 
 						precision("discount_percentage", item));
@@ -993,20 +993,12 @@
 
 	get_terms: function() {
 		var me = this;
-		if(this.frm.doc.tc_name) {
-			return frappe.call({
-				method: 'erpnext.setup.doctype.terms_and_conditions.terms_and_conditions.get_terms_and_conditions',
-				args: {
-					template_name: this.frm.doc.tc_name,
-					doc: this.frm.doc
-				},
-				callback: function(r) {
-					if(!r.exc) {
-						me.frm.set_value("terms", r.message);
-					}
-				}
-			});
-		}
+
+		erpnext.utils.get_terms(this.frm.doc.tc_name, this.frm.doc, function(r) {
+			if(!r.exc) {
+				me.frm.set_value("terms", r.message);
+			}
+		});
 	},
 
 	taxes_and_charges: function() {
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 66daf0d..1eeb896 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -48,15 +48,15 @@
 		report.page.add_inner_button(__("Balance Sheet"), function() {
 			var filters = report.get_values();
 			frappe.set_route('query-report', 'Balance Sheet', {company: filters.company});
-		}, 'Financial Statements');
+		}, __('Financial Statements'));
 		report.page.add_inner_button(__("Profit and Loss"), function() {
 			var filters = report.get_values();
 			frappe.set_route('query-report', 'Profit and Loss Statement', {company: filters.company});
-		}, 'Financial Statements');
+		}, __('Financial Statements'));
 		report.page.add_inner_button(__("Cash Flow Statement"), function() {
 			var filters = report.get_values();
 			frappe.set_route('query-report', 'Cash Flow', {company: filters.company});
-		}, 'Financial Statements');
+		}, __('Financial Statements'));
 	}
 };
 
diff --git a/erpnext/public/js/pos/pos.html b/erpnext/public/js/pos/pos.html
index 181a41f..6065e60 100644
--- a/erpnext/public/js/pos/pos.html
+++ b/erpnext/public/js/pos/pos.html
@@ -68,12 +68,12 @@
 
 				</div>
 				<div class="col-xs-6 numeric_keypad hidden-xs" style="display:none">
-					{% var chartData = [__("Qty"), __("Disc"), __("Price")] %} {% for(var i=0; i
+					{% var chartData = ["Qty", "Disc", "Price"] %} {% for(var i=0; i
 					<3; i++) { %} <div class="row text-right">
 						{% for(var j=i*3; j
 						<(i+1)*3; j++) { %} <button type="button" class="btn btn-default numeric-keypad" val="{{j+1}}">{{j+1}}</button>
 							{% } %}
-							<button type="button" id="pos-item-{{ chartData[i].toLowerCase() }}" class="btn text-center btn-default numeric-keypad pos-operation">{{ chartData[i] }}</button>
+							<button type="button" id="pos-item-{{ chartData[i].toLowerCase() }}" class="btn text-center btn-default numeric-keypad pos-operation">{{ __(chartData[i]) }}</button>
 				</div>
 				{% } %}
 				<div class="row text-right">
@@ -120,8 +120,8 @@
 				<div class="cell search-item"></div>
 			</div>
 			<div class="app-listing item-list image-view-container">
-				
+
 			</div>
 		</div>
 	</div>
-</div>
\ No newline at end of file
+</div>
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 3a2254e..d618fbe 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -104,6 +104,21 @@
 			}
 		}
 		refresh_field(table_fieldname);
+	},
+
+	get_terms: function(tc_name, doc, callback) {
+		if(tc_name) {
+			return frappe.call({
+				method: 'erpnext.setup.doctype.terms_and_conditions.terms_and_conditions.get_terms_and_conditions',
+				args: {
+					template_name: tc_name,
+					doc: doc
+				},
+				callback: function(r) {
+					callback(r)
+				}
+			});
+		}
 	}
 });
 
diff --git a/erpnext/schools/doctype/grade_interval/__init__.py b/erpnext/schools/doctype/grade_interval/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/schools/doctype/grade_interval/__init__.py
+++ /dev/null
diff --git a/erpnext/schools/doctype/grade_interval/grade_interval.json b/erpnext/schools/doctype/grade_interval/grade_interval.json
deleted file mode 100644
index c9c5949..0000000
--- a/erpnext/schools/doctype/grade_interval/grade_interval.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2016-08-26 03:11:09.591049", 
- "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": "grade_code", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Grade Code", 
-   "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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "min_score", 
-   "fieldtype": "Percent", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Min Score", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "1", 
-   "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
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "grade_description", 
-   "fieldtype": "Small Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Grade Description", 
-   "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
-  }
- ], 
- "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-12-14 12:54:56.902465", 
- "modified_by": "Administrator", 
- "module": "Schools", 
- "name": "Grade Interval", 
- "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/schools/doctype/grade_interval/grade_interval.py b/erpnext/schools/doctype/grade_interval/grade_interval.py
deleted file mode 100644
index c8ded13..0000000
--- a/erpnext/schools/doctype/grade_interval/grade_interval.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- 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 GradeInterval(Document):
-    def validate(self):
-        pass
\ No newline at end of file
diff --git a/erpnext/schools/doctype/grading_structure/__init__.py b/erpnext/schools/doctype/grading_structure/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/schools/doctype/grading_structure/__init__.py
+++ /dev/null
diff --git a/erpnext/schools/doctype/grading_structure/grading_structure.js b/erpnext/schools/doctype/grading_structure/grading_structure.js
deleted file mode 100644
index 36f4504..0000000
--- a/erpnext/schools/doctype/grading_structure/grading_structure.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Grading Structure', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/schools/doctype/grading_structure/grading_structure.json b/erpnext/schools/doctype/grading_structure/grading_structure.json
deleted file mode 100644
index 3c30f29..0000000
--- a/erpnext/schools/doctype/grading_structure/grading_structure.json
+++ /dev/null
@@ -1,202 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 1, 
- "autoname": "field:grading_system_name", 
- "beta": 0, 
- "creation": "2016-08-26 03:06:53.922972", 
- "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": "grading_system_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Grading System Name", 
-   "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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_2", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 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": "description", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "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": "grading_intervals_section", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Grading Intervals", 
-   "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": "grade_intervals", 
-   "fieldtype": "Table", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Grade Intervals", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Grade Interval", 
-   "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": 0, 
- "max_attachments": 0, 
- "modified": "2016-12-14 12:35:39.690256", 
- "modified_by": "Administrator", 
- "module": "Schools", 
- "name": "Grading Structure", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "is_custom": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Academics User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "title_field": "grading_system_name", 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/schools/doctype/grading_structure/grading_structure.py b/erpnext/schools/doctype/grading_structure/grading_structure.py
deleted file mode 100644
index 1b5d6a8..0000000
--- a/erpnext/schools/doctype/grading_structure/grading_structure.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- 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
-from frappe import _
-from frappe.utils import cstr
-
-class GradingStructure(Document):
-    def validate(self):
-        grade_intervals = self.get("grade_intervals")
-        check_overlap(grade_intervals, self)
-
-#Check if any of the grade intervals for this grading structure overlap
-def check_overlap(grade_intervals, parent_doc):
-    for interval1 in grade_intervals:
-        for interval2 in grade_intervals:
-            if interval1.name == interval2.name:
-                pass
-            else:
-                if (interval1.from_score <= interval2.from_score and interval1.to_score >= interval2.from_score) or (interval1.from_score <= interval2.to_score and interval1.to_score >= interval2.to_score):
-                    frappe.throw(_("""The intervals for Grade Code {0} overlaps with the grade intervals for other grades. 
-                    Please check intervals {0} and {1} and try again""".format(interval1.grade_code, interval2.grade_code))) 
\ No newline at end of file
diff --git a/erpnext/schools/doctype/grading_structure/test_grading_structure.py b/erpnext/schools/doctype/grading_structure/test_grading_structure.py
deleted file mode 100644
index 0e36080..0000000
--- a/erpnext/schools/doctype/grading_structure/test_grading_structure.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import frappe
-import unittest
-
-# test_records = frappe.get_test_records('Grading Structure')
-
-class TestGradingStructure(unittest.TestCase):
-	pass
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index e4101af..c0b3b84 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -10,8 +10,7 @@
 from frappe.desk.reportview import build_match_conditions
 from erpnext.utilities.transaction_base import TransactionBase
 from frappe.geo.address_and_contact import load_address_and_contact, delete_contact_and_address
-from erpnext.accounts.party import validate_party_accounts, get_timeline_data # keep this
-from erpnext import get_default_currency
+from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
 
 class Customer(TransactionBase):
 	def get_feed(self):
@@ -23,25 +22,9 @@
 		self.load_dashboard_info()
 
 	def load_dashboard_info(self):
-		billing_this_year = frappe.db.sql("""
-			select sum(debit_in_account_currency) - sum(credit_in_account_currency), account_currency
-			from `tabGL Entry`
-			where voucher_type='Sales Invoice' and party_type = 'Customer'
-				and party=%s and fiscal_year = %s""",
-			(self.name, frappe.db.get_default("fiscal_year")))
-
-		total_unpaid = frappe.db.sql("""select sum(outstanding_amount)
-			from `tabSales Invoice`
-			where customer=%s and docstatus = 1""", self.name)
-
-		info = {}
-		info["billing_this_year"] = billing_this_year[0][0] if billing_this_year else 0
-		info["currency"] = billing_this_year[0][1] if billing_this_year else get_default_currency()
-		info["total_unpaid"] = total_unpaid[0][0] if total_unpaid else 0
-
+		info = get_dashboard_info(self.doctype, self.name)
 		self.set_onload('dashboard_info', info)
 
-
 	def autoname(self):
 		cust_master_name = frappe.defaults.get_global_default('cust_master_name')
 		if cust_master_name == 'Customer Name':
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 7154343..1fcf334 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -348,9 +348,13 @@
 	margin_type: function(doc, cdt, cdn){
 		// calculate the revised total margin and rate on margin type changes
 		item = locals[cdt][cdn];
-		this.apply_pricing_rule_on_item(item, doc,cdt, cdn)
-		this.calculate_taxes_and_totals();
-		cur_frm.refresh_fields();
+		if(!item.margin_type) {
+			frappe.model.set_value(cdt, cdn, "margin_rate_or_amount", 0);
+		} else {
+			this.apply_pricing_rule_on_item(item, doc,cdt, cdn)
+			this.calculate_taxes_and_totals();
+			cur_frm.refresh_fields();
+		}
 	}
 });
 
diff --git a/erpnext/setup/doctype/email_digest/templates/default.html b/erpnext/setup/doctype/email_digest/templates/default.html
index 78acbd9..5a657d2 100644
--- a/erpnext/setup/doctype/email_digest/templates/default.html
+++ b/erpnext/setup/doctype/email_digest/templates/default.html
@@ -1,6 +1,6 @@
 {% macro show_card(card) %}
 <div style="width: 50%; float:left; min-height: 80px; padding-top: 20px;">
-    <h6 style="color: {{ text_muted }}; font-size: 12px; margin-bottom: 0px; margin-top: 0px;">{{ card.label }}
+    <h6 style="color: {{ text_muted }}; font-size: 12px; margin-bottom: 0px; margin-top: 0px;">{{ _(card.label) }}
     {% if card.count %}
     <span class="badge">({{ card.count }})</span>
     {% endif %}</h6>
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index 55a0fd3..46e4d5c 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -91,8 +91,7 @@
 			response.raise_for_status()
 			value = response.json()["rates"][to_currency]
 			cache.setex(key, value, 6 * 60 * 60)
-
 		return flt(value)
 	except:
-		frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}").format(from_currency, to_currency, transaction_date))
+		frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually").format(from_currency, to_currency, transaction_date))
 		return 0.0
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 057c78a..86c6959 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -44,8 +44,6 @@
 			}, __("View"));
 		}
 
-		// make sensitive fields(has_serial_no, is_stock_item, valuation_method)
-		// read only if any stock ledger entry exists
 		if(!frm.doc.is_fixed_asset) {
 			erpnext.item.make_dashboard(frm);
 		}
@@ -77,6 +75,8 @@
 
 		erpnext.item.edit_prices_button(frm);
 
+		// make sensitive fields(has_serial_no, is_stock_item, valuation_method, has_batch_no)
+		// read only if any stock ledger entry exists
 		if (!frm.doc.__islocal && frm.doc.is_stock_item) {
 			frm.toggle_enable(['has_serial_no', 'is_stock_item', 'valuation_method', 'has_batch_no'],
 				(frm.doc.__onload && frm.doc.__onload.sle_exists=="exists") ? false : true);
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
index b97378f..3ae61b9 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
@@ -102,9 +102,17 @@
 				total_item_cost += flt(d[based_on])
 			});
 
+			var total_charges = 0.0;
 			$.each(this.frm.doc.items || [], function(i, item) {
 				item.applicable_charges = flt(item[based_on]) * flt(me.frm.doc.total_taxes_and_charges) / flt(total_item_cost)			
+				item.applicable_charges = flt(item.applicable_charges, precision("applicable_charges", item))
+				total_charges += item.applicable_charges
 			});
+
+			if (total_charges != this.frm.doc.total_taxes_and_charges){
+				var diff = this.frm.doc.total_taxes_and_charges - flt(total_charges)
+				this.frm.doc.items.slice(-1)[0].applicable_charges += diff
+			}
 			refresh_field("items");
 		}
 	},
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 0eaf5ba..a2d3606 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -5,6 +5,7 @@
 import frappe
 from frappe import _
 from frappe.utils import flt
+from frappe.model.meta import get_field_precision
 from frappe.model.document import Document
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
@@ -71,7 +72,17 @@
 		if not total:
 			frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on))
 		
-		if self.total_taxes_and_charges != sum([flt(d.applicable_charges) for d in self.get("items")]):
+		total_applicable_charges = sum([flt(d.applicable_charges) for d in self.get("items")])
+
+		precision = get_field_precision(frappe.get_meta("Landed Cost Item").get_field("applicable_charges"),
+		currency=frappe.db.get_value("Company", self.company, "default_currency", cache=True))
+
+		diff = flt(self.total_taxes_and_charges) - flt(total_applicable_charges)
+		diff = flt(diff, precision)
+
+		if abs(diff) < (2.0 / (10**precision)):
+			self.items[-1].applicable_charges += diff
+		else:
 			frappe.throw(_("Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges"))
 
 
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 930896c..eb8f8b8 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -7,7 +7,7 @@
 import frappe
 from frappe.utils import flt
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
-	import set_perpetual_inventory, get_gl_entries, test_records as pr_test_records
+	import set_perpetual_inventory, get_gl_entries, test_records as pr_test_records, make_purchase_receipt
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 
 class TestLandedCostVoucher(unittest.TestCase):
@@ -133,7 +133,29 @@
 
 		set_perpetual_inventory(0)
 
-def submit_landed_cost_voucher(receipt_document_type, receipt_document):
+	def test_landed_cost_voucher_for_odd_numbers (self):
+		set_perpetual_inventory(1)
+
+		pr = make_purchase_receipt(do_not_save=True)
+		pr.items[0].cost_center = "_Test Company - _TC"
+		for x in range(2):
+			pr.append("items", {
+				"item_code": "_Test Item",
+				"warehouse": "_Test Warehouse - _TC",
+				"cost_center": "_Test Company - _TC",
+				"qty": 5,
+				"rate": 50
+			})
+		pr.submit()
+
+		lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, 123.22)
+		
+		self.assertEquals(lcv.items[0].applicable_charges, 41.07)
+		self.assertEquals(lcv.items[2].applicable_charges, 41.08)		
+		
+		set_perpetual_inventory(0)
+
+def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges=50):
 	ref_doc = frappe.get_doc(receipt_document_type, receipt_document)
 	
 	lcv = frappe.new_doc("Landed Cost Voucher")
@@ -151,7 +173,7 @@
 	lcv.set("taxes", [{
 		"description": "Insurance Charges",
 		"account": "_Test Account Insurance Charges - _TC",
-		"amount": 50
+		"amount": charges
 	}])
 
 	lcv.insert()
@@ -159,11 +181,15 @@
 	distribute_landed_cost_on_items(lcv)
 	
 	lcv.submit()
+
+	return lcv
 		
 def distribute_landed_cost_on_items(lcv):
 	based_on = lcv.distribute_charges_based_on.lower()
 	total = sum([flt(d.get(based_on)) for d in lcv.get("items")])
+
 	for item in lcv.get("items"):
 		item.applicable_charges = flt(item.get(based_on)) * flt(lcv.total_taxes_and_charges) / flt(total)
+		item.applicable_charges = flt(item.applicable_charges, lcv.precision("applicable_charges", item))
 
 test_records = frappe.get_test_records('Landed Cost Voucher')
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 58c16e1..b386a47 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -33,7 +33,7 @@
 	"qty": function(frm, doctype, name) {
 			var d = locals[doctype][name];
 			if (flt(d.qty) < flt(d.min_order_qty)) {
-				alert(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
+				frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
 			}
 		}
 	}
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 055b9c4..c11965e 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -74,7 +74,8 @@
 			"Purchase Order Item": {
 				"ref_dn_field": "purchase_order_item",
 				"compare_fields": [["project", "="], ["uom", "="], ["item_code", "="]],
-				"is_child_table": True
+				"is_child_table": True,
+				"allow_duplicate_prev_row_id": True
 			}
 		})
 
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index 3b65ff8..b37713b 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -1,5 +1,6 @@
 {
  "allow_copy": 0, 
+ "allow_guest_to_view": 0, 
  "allow_import": 1, 
  "allow_rename": 1, 
  "autoname": "field:serial_no", 
@@ -13,6 +14,7 @@
  "editable_grid": 0, 
  "fields": [
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -42,6 +44,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -69,6 +72,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, 
@@ -130,6 +135,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -162,6 +168,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -189,6 +196,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -217,6 +225,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -248,6 +257,7 @@
    "width": "300px"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -280,6 +290,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -311,6 +322,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -339,6 +351,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -367,6 +380,7 @@
    "width": "50%"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -396,6 +410,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -425,6 +440,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -455,6 +471,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -483,6 +500,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -514,6 +532,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -542,6 +561,7 @@
    "width": "50%"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -571,6 +591,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 1, 
    "collapsible": 0, 
@@ -599,6 +620,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -628,6 +650,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -657,6 +680,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -686,6 +710,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -716,6 +741,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -744,6 +770,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -775,6 +802,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -803,6 +831,7 @@
    "width": "50%"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -834,6 +863,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 1, 
    "collapsible": 0, 
@@ -864,6 +894,68 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "invoice_details", 
+   "fieldtype": "Section Break", 
+   "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": "Invoice Details", 
+   "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_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "sales_invoice", 
+   "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": "Sales Invoice", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Sales Invoice", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -892,6 +984,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -920,6 +1013,7 @@
    "width": "50%"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -952,6 +1046,7 @@
    "width": "150px"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -983,6 +1078,7 @@
    "width": "150px"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -1011,6 +1107,7 @@
    "width": "50%"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -1042,6 +1139,7 @@
    "width": "150px"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -1073,6 +1171,7 @@
    "width": "150px"
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -1101,6 +1200,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -1129,6 +1229,7 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -1158,18 +1259,18 @@
    "unique": 0
   }
  ], 
+ "has_web_view": 0, 
  "hide_heading": 0, 
  "hide_toolbar": 0, 
  "icon": "fa fa-barcode", 
  "idx": 1, 
  "image_view": 0, 
  "in_create": 0, 
- "in_dialog": 0, 
  "is_submittable": 0, 
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2017-02-20 13:26:55.864960", 
+ "modified": "2017-05-15 18:22:23.685286", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Serial No", 
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 68d64a2..d9d9568 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -26,3 +26,19 @@
 		# show/hide barcode field
 		frappe.make_property_setter({'fieldname': 'barcode', 'property': 'hidden',
 			'value': 0 if self.show_barcode_field else 1})
+			
+		self.cant_change_valuation_method()
+		
+	def cant_change_valuation_method(self):
+		db_valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
+		
+		if db_valuation_method and db_valuation_method != self.valuation_method:
+			# check if there are any stock ledger entries against items 
+			# which does not have it's own valuation method
+			sle = frappe.db.sql("""select name from `tabStock Ledger Entry` sle
+				where exists(select name from tabItem 
+					where name=sle.item_code and (valuation_method is null or valuation_method=''))
+			""")
+			
+			if sle:
+				frappe.throw(_("Can't change valuation method, as there are transactions against some items which does not have it's own valuation method"))
\ No newline at end of file