Merge pull request #21618 from scmmishra/sync-dashboards

diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 786b9cf..38d8a62 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '12.0.0-dev'
+__version__ = '13.0.0-dev'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py
index 3f3174a..7ecdc41 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/payment_order.py
@@ -80,7 +80,7 @@
 			paid_amt += d.amount
 
 	je.append('accounts', {
-		'account': doc.references[0].account,
+		'account': doc.account,
 		'credit_in_account_currency': paid_amt
 	})
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index fb1a4f4..bfe35ab 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -297,7 +297,8 @@
 			fields = ["*"],
 			filters = {
 				"voucher_type": voucher_type,
-				"voucher_no": voucher_no
+				"voucher_no": voucher_no,
+				"is_cancelled": 0
 			})
 
 	if gl_entries:
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
index 1c45810..714e48d 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
@@ -9,8 +9,8 @@
 import copy
 
 def execute(filters=None):
-	period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
-		filters.periodicity, filters.accumulated_values, filters.company)
+	period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.period_start_date,
+		filters.period_end_date, filters.filter_based_on, filters.periodicity, filters.accumulated_values, filters.company)
 
 	columns, data = [], []
 
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 74b3582..ec7d14d 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -153,7 +153,7 @@
 		if not self.lead_name:
 			self.set_lead_name()
 
-		names = self.lead_name.split(" ")
+		names = self.lead_name.strip().split(" ")
 		if len(names) > 1:
 			first_name, last_name = names[0], " ".join(names[1:])
 		else:
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
index 50b98e9..263005e 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
@@ -62,6 +62,8 @@
 				callback : function(r) {
 					window.location.href = r.message;
 				}
+			}).fail(function() {
+				frappe.dom.unfreeze();
 			});
 		}
 	},
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index 5df35df..bdde9ee 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -15,7 +15,7 @@
 		params = urlencode({
 			"response_type":"code",
 			"client_id": self.consumer_key,
-			"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
+			"redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?",
 			"scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
 		})
 
@@ -30,7 +30,7 @@
 			"code": code,
 			"client_id": self.consumer_key,
 			"client_secret": self.get_password(fieldname="consumer_secret"),
-			"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
+			"redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?",
 		}
 		headers = {
 			"Content-Type": "application/x-www-form-urlencoded"
@@ -154,7 +154,7 @@
 
 		return response
 
-@frappe.whitelist()
+@frappe.whitelist(allow_guest=True)
 def callback(code=None, error=None, error_description=None):
 	if not error:
 		linkedin_settings = frappe.get_doc("LinkedIn Settings")
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.js b/erpnext/crm/doctype/twitter_settings/twitter_settings.js
index b55946a..f6f431c 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.js
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.js
@@ -47,6 +47,8 @@
 				callback : function(r) {
 					window.location.href = r.message;
 				}
+			}).fail(function() {
+				frappe.dom.unfreeze();
 			});
 		}
 	},
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
index 64f53b5..7616b4c 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -12,13 +12,12 @@
 
 class TwitterSettings(Document):
 	def get_authorize_url(self):
-		callback_url = "{0}/?cmd=erpnext.crm.doctype.twitter_settings.twitter_settings.callback".format(frappe.utils.get_url())
+		callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url())
 		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
-
 		try:
 			redirect_url = auth.get_authorization_url()
 			return redirect_url
-		except:
+		except tweepy.TweepError as e:
 			frappe.msgprint(_("Error! Failed to get request token."))
 			frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
 
@@ -91,8 +90,12 @@
 				frappe.db.commit()
 			frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason))
 
-@frappe.whitelist()
-def callback(oauth_token, oauth_verifier):
-	twitter_settings = frappe.get_single("Twitter Settings")
-	twitter_settings.get_access_token(oauth_token,oauth_verifier)
-	frappe.db.commit()
+@frappe.whitelist(allow_guest=True)
+def callback(oauth_token = None, oauth_verifier = None):
+	if oauth_token and oauth_verifier:
+		twitter_settings = frappe.get_single("Twitter Settings")
+		twitter_settings.get_access_token(oauth_token,oauth_verifier)
+		frappe.db.commit()
+	else:
+		frappe.local.response["type"] = "redirect"
+		frappe.local.response["location"] = get_url_to_form("Twitter Settings","Twitter Settings")
diff --git a/erpnext/crm/utils.py b/erpnext/crm/utils.py
index 38bf79e..95b19ec 100644
--- a/erpnext/crm/utils.py
+++ b/erpnext/crm/utils.py
@@ -19,6 +19,5 @@
 					mobile_no = primary_mobile_nos[0]
 
 			lead = frappe.get_doc("Lead", contact_lead)
-			lead.phone = phone
-			lead.mobile_no = mobile_no
-			lead.save()
+			lead.db_set("phone", phone)
+			lead.db_set("mobile_no", mobile_no)
diff --git a/erpnext/education/doctype/education_settings/education_settings.json b/erpnext/education/doctype/education_settings/education_settings.json
index 967a030..0e548db 100644
--- a/erpnext/education/doctype/education_settings/education_settings.json
+++ b/erpnext/education/doctype/education_settings/education_settings.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2017-04-05 13:33:04.519313",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -42,12 +43,14 @@
    "fieldtype": "Column Break"
   },
   {
+   "default": "0",
    "description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.",
    "fieldname": "validate_batch",
    "fieldtype": "Check",
    "label": "Validate Batch for Students in Student Group"
   },
   {
+   "default": "0",
    "description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.",
    "fieldname": "validate_course",
    "fieldtype": "Check",
@@ -74,13 +77,13 @@
   {
    "fieldname": "web_academy_settings_section",
    "fieldtype": "Section Break",
-   "label": "LMS Settings"
+   "label": "Learning Management System Settings"
   },
   {
    "depends_on": "eval: doc.enable_lms",
    "fieldname": "portal_title",
    "fieldtype": "Data",
-   "label": "LMS Title"
+   "label": "Learning Management System Title"
   },
   {
    "depends_on": "eval: doc.enable_lms",
@@ -89,9 +92,10 @@
    "label": "Description"
   },
   {
+   "default": "0",
    "fieldname": "enable_lms",
    "fieldtype": "Check",
-   "label": "Enable LMS"
+   "label": "Enable Learning Management System"
   },
   {
    "default": "0",
@@ -102,7 +106,8 @@
   }
  ],
  "issingle": 1,
- "modified": "2019-05-13 18:36:13.127563",
+ "links": [],
+ "modified": "2020-05-07 19:18:10.639356",
  "modified_by": "Administrator",
  "module": "Education",
  "name": "Education Settings",
@@ -141,4 +146,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
index f75bb41..61faea1 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
@@ -145,7 +145,7 @@
 
 	def remove_holidays(rows):
 		rows = [ row for row in rows if row[4] != "Holiday"]
-		return
+		return rows
 
 	from frappe.modules import scrub
 
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index d98ed1b..82ed277 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -215,7 +215,7 @@
 def get_employee_details(group_by, company):
 	emp_map = {}
 	query = """select name, employee_name, designation, department, branch, company,
-		holiday_list from `tabEmployee` where company = '%s' """ % frappe.db.escape(company)
+		holiday_list from `tabEmployee` where company = %s """ % frappe.db.escape(company)
 
 	if group_by:
 		group_by = group_by.lower()
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index c550d49..76e10e5 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -248,8 +248,7 @@
 	for loan_security in loan_security_pledge_details:
 		unpledge_request.append('securities', {
 			"loan_security": loan_security.loan_security,
-			"qty": loan_security.qty,
-			"against_pledge": loan_security.parent
+			"qty": loan_security.qty
 		})
 
 	if as_dict:
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 77a1fcc..364e2ff 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -15,6 +15,7 @@
 from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
 from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
 from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
+from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
 
 class TestLoan(unittest.TestCase):
 	def setUp(self):
@@ -152,7 +153,7 @@
 		repayment_entry.save()
 		repayment_entry.submit()
 
-		penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
+		penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year))
 		self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
 
 		amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
@@ -305,7 +306,7 @@
 		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
 		process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
 
-		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
 			"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
 		repayment_entry.submit()
 
@@ -319,13 +320,12 @@
 		unpledge_request.submit()
 		unpledge_request.status = 'Approved'
 		unpledge_request.save()
-
-		loan_security_pledge.load_from_db()
 		loan.load_from_db()
 
+		pledged_qty = get_pledged_security_qty(loan.name)
+
 		self.assertEqual(loan.status, 'Closed')
-		for security in loan_security_pledge.securities:
-			self.assertEquals(security.qty, 0)
+		self.assertEquals(sum(pledged_qty.values()), 0)
 
 
 def create_loan_accounts():
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 452c836..2ab668a 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -264,6 +264,7 @@
 	penalty_amount = 0
 	payable_principal_amount = 0
 	final_due_date = ''
+	due_date = ''
 
 	for entry in accrued_interest_entries:
 		# Loan repayment due date is one day after the loan interest is accrued
@@ -272,7 +273,7 @@
 
 		due_date = add_days(entry.posting_date, 1)
 		no_of_late_days = date_diff(posting_date,
-					add_days(due_date, loan_type_details.grace_period_in_days)) + 1
+					add_days(due_date, loan_type_details.grace_period_in_days))
 
 		if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary):
 			penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365
@@ -290,9 +291,9 @@
 
 	pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
 
-	if payment_type == "Loan Closure" and not payable_principal_amount:
-		if final_due_date:
-			pending_days = date_diff(posting_date, final_due_date)
+	if payment_type == "Loan Closure":
+		if due_date:
+			pending_days = date_diff(posting_date, due_date) + 1
 		else:
 			pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1
 
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 8ca6e3e..308c438 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -69,7 +69,7 @@
 		loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100)
 
 	for loan, value in iteritems(loan_security_map):
-		if (value["security_value"]/value["loan_amount"]) < ltv_ratio:
+		if (value["loan_amount"]/value['security_value'] * 100) > ltv_ratio:
 			create_loan_security_shortfall(loan, value, process_loan_security_shortfall)
 
 def create_loan_security_shortfall(loan, value, process_loan_security_shortfall):
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js
index 72c5f38..8223206 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js
@@ -4,10 +4,8 @@
 frappe.ui.form.on('Loan Security Unpledge', {
 	refresh: function(frm) {
 
-		frm.set_query("against_pledge", "securities", () => {
-			return {
-				filters : [["status", "in", ["Pledged", "Partially Pledged"]]]
-			};
-		});
+		if (frm.doc.docstatus == 1 && frm.doc.status == 'Approved') {
+			frm.set_df_property('status', 'read_only', 1);
+		}
 	}
 });
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index b2bb22a..5e9d82a 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -8,12 +8,13 @@
 from frappe.model.document import Document
 from frappe.utils import get_datetime, flt
 import json
+from six import iteritems
 from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price
 
 class LoanSecurityUnpledge(Document):
 	def validate(self):
-		self.validate_pledges()
 		self.validate_duplicate_securities()
+		self.validate_unpledge_qty()
 
 	def on_cancel(self):
 		self.update_loan_security_pledge(cancel=1)
@@ -23,80 +24,52 @@
 	def validate_duplicate_securities(self):
 		security_list = []
 		for d in self.securities:
-			security = [d.loan_security, d.against_pledge]
-			if security not in security_list:
-				security_list.append(security)
+			if d.loan_security not in security_list:
+				security_list.append(d.loan_security)
 			else:
-				frappe.throw(_("Row {0}: Loan Security {1} against Loan Security Pledge {2} added multiple times").format(
-					d.idx, frappe.bold(d.loan_security), frappe.bold(d.against_pledge)))
+				frappe.throw(_("Row {0}: Loan Security {1} added multiple times").format(
+					d.idx, frappe.bold(d.loan_security)))
 
-	def validate_pledges(self):
-		pledge_qty_map = self.get_pledge_details()
-		loan = frappe.get_doc("Loan", self.loan)
+	def validate_unpledge_qty(self):
+		pledge_qty_map = get_pledged_security_qty(self.loan)
 
-		remaining_qty = 0
-		unpledge_value = 0
+		ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
+			fields=["name", "loan_to_value_ratio"], as_list=1))
+
+		loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price",
+			fields=["loan_security", "loan_security_price"],
+			filters = {
+				"valid_from": ("<=", get_datetime()),
+				"valid_upto": (">=", get_datetime())
+			}, as_list=1))
+
+		loan_amount, principal_paid = frappe.get_value("Loan", self.loan, ['loan_amount', 'total_principal_paid'])
+		pending_principal_amount = loan_amount - principal_paid
+		security_value = 0
 
 		for security in self.securities:
-			pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0)
-			if not pledged_qty:
-				frappe.throw(_("Zero qty of {0} pledged against loan {1}").format(frappe.bold(security.loan_security),
-					frappe.bold(self.loan)))
+			pledged_qty = pledge_qty_map.get(security.loan_security)
 
-			unpledge_qty = pledged_qty - security.qty
-			security_price = security.qty * get_loan_security_price(security.loan_security)
+			if security.qty > pledged_qty:
+				frappe.throw(_("""Row {0}: {1} {2} of {3} is pledged against Loan {4}.
+					You are trying to unpledge more""").format(security.idx, pledged_qty, security.uom,
+					frappe.bold(security.loan_security), frappe.bold(self.loan)))
 
-			if unpledge_qty < 0:
-				frappe.throw(_("""Row {0}: Cannot unpledge more than {1} qty of {2} against
-					Loan Security Pledge {3}""").format(security.idx, frappe.bold(pledged_qty),
-					frappe.bold(security.loan_security), frappe.bold(security.against_pledge)))
+			qty_after_unpledge = pledged_qty - security.qty
+			ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
 
-			remaining_qty += unpledge_qty
-			unpledge_value += security_price - flt(security_price * security.haircut/100)
+			security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security)
 
-		if unpledge_value > loan.total_principal_paid:
-			frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount"))
+		if not security_value and pending_principal_amount > 0:
+			frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
 
-	def get_pledge_details(self):
-		pledge_qty_map = {}
-
-		pledge_details = frappe.db.sql("""
-			SELECT p.parent, p.loan_security, p.qty FROM
-				`tabLoan Security Pledge` lsp,
-				`tabPledge` p
-			WHERE
-				p.parent = lsp.name
-				AND lsp.loan = %s
-				AND lsp.docstatus = 1
-				AND lsp.status in ('Pledged', 'Partially Pledged')
-		""", (self.loan), as_dict=1)
-
-		for pledge in pledge_details:
-			pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty)
-
-		return pledge_qty_map
+		if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio:
+			frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
 
 	def on_update_after_submit(self):
 		if self.status == "Approved":
-			self.update_loan_security_pledge()
 			self.update_loan_status()
-
-	def update_loan_security_pledge(self, cancel=0):
-		if cancel:
-			new_qty = 'p.qty + u.qty'
-		else:
-			new_qty = 'p.qty - u.qty'
-
-		frappe.db.sql("""
-			UPDATE
-				`tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp, `tabLoan Security Unpledge` lsu
-					SET p.qty = {new_qty}
-			WHERE
-				lsp.loan = %s
-				AND p.parent = u.against_pledge
-				AND p.parent = lsp.name
-				AND lsp.docstatus = 1
-				AND p.loan_security = u.loan_security""".format(new_qty=new_qty),(self.loan))
+			self.db_set('unpledge_time', get_datetime())
 
 	def update_loan_status(self, cancel=0):
 		if cancel:
@@ -104,10 +77,45 @@
 			if loan_status == 'Closed':
 				frappe.db.set_value('Loan', self.loan, 'status', 'Loan Closure Requested')
 		else:
-			pledge_qty = frappe.db.sql("""SELECT SUM(c.qty)
-				FROM `tabLoan Security Pledge` p, `tabPledge` c
-				WHERE p.loan = %s AND c.parent = p.name""", (self.loan))[0][0]
+			pledged_qty = 0
+			current_pledges = get_pledged_security_qty(self.loan)
 
-			if not pledge_qty:
+			for security, qty in iteritems(current_pledges):
+				pledged_qty += qty
+
+			if not pledged_qty:
 				frappe.db.set_value('Loan', self.loan, 'status', 'Closed')
 
+@frappe.whitelist()
+def get_pledged_security_qty(loan):
+
+	current_pledges = {}
+
+	unpledges = frappe._dict(frappe.db.sql("""
+		SELECT u.loan_security, sum(u.qty) as qty
+		FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
+		WHERE up.loan = %s
+		AND u.parent = up.name
+		AND up.status = 'Approved'
+		GROUP BY u.loan_security
+	""", (loan)))
+
+	pledges = frappe._dict(frappe.db.sql("""
+		SELECT p.loan_security, sum(p.qty) as qty
+		FROM `tabLoan Security Pledge` lp, `tabPledge`p
+		WHERE lp.loan = %s
+		AND p.parent = lp.name
+		AND lp.status = 'Pledged'
+		GROUP BY p.loan_security
+	""", (loan)))
+
+	for security, qty in iteritems(pledges):
+		current_pledges.setdefault(security, qty)
+		current_pledges[security] -= unpledges.get(security, 0.0)
+
+	return current_pledges
+
+
+
+
+
diff --git a/erpnext/loan_management/doctype/unpledge/unpledge.json b/erpnext/loan_management/doctype/unpledge/unpledge.json
index 9e6277d..ee192d7 100644
--- a/erpnext/loan_management/doctype/unpledge/unpledge.json
+++ b/erpnext/loan_management/doctype/unpledge/unpledge.json
@@ -1,11 +1,11 @@
 {
+ "actions": [],
  "creation": "2019-09-21 13:22:19.793797",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
   "loan_security",
-  "against_pledge",
   "loan_security_type",
   "loan_security_code",
   "haircut",
@@ -55,14 +55,6 @@
    "reqd": 1
   },
   {
-   "fieldname": "against_pledge",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Against Pledge",
-   "options": "Loan Security Pledge",
-   "reqd": 1
-  },
-  {
    "fetch_from": "loan_security.haircut",
    "fieldname": "haircut",
    "fieldtype": "Percent",
@@ -71,7 +63,8 @@
   }
  ],
  "istable": 1,
- "modified": "2019-10-02 12:48:18.588236",
+ "links": [],
+ "modified": "2020-05-06 10:50:18.448552",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Unpledge",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 84bfab2..8301f30 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -421,6 +421,9 @@
 		return holidays[holiday_list]
 
 	def update_operation_status(self):
+		allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order"))
+		max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage/100 * flt(self.qty))
+
 		for d in self.get("operations"):
 			if not d.completed_qty:
 				d.status = "Pending"
@@ -428,6 +431,8 @@
 				d.status = "Work in Progress"
 			elif flt(d.completed_qty) == flt(self.qty):
 				d.status = "Completed"
+			elif flt(d.completed_qty) <= max_allowed_qty_for_wo:
+				d.status = "Completed"
 			else:
 				frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 15235f1..3f90d36 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -495,6 +495,7 @@
 execute:frappe.delete_doc('DocType', 'Production Planning Tool', ignore_missing=True)
 erpnext.patches.v10_0.migrate_daily_work_summary_settings_to_daily_work_summary_group # 24-12-2018
 erpnext.patches.v10_0.add_default_cash_flow_mappers
+erpnext.patches.v11_0.rename_duplicate_item_code_values
 erpnext.patches.v11_0.make_quality_inspection_template
 erpnext.patches.v10_0.update_status_for_multiple_source_in_po
 erpnext.patches.v10_0.set_auto_created_serial_no_in_stock_entry
@@ -678,3 +679,4 @@
 erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
 erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
 erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
+execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
diff --git a/erpnext/patches/v11_0/rename_duplicate_item_code_values.py b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py
new file mode 100644
index 0000000..00ab562
--- /dev/null
+++ b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py
@@ -0,0 +1,8 @@
+import frappe
+
+def execute():
+	items = []
+	items = frappe.db.sql("""select item_code from `tabItem` group by item_code having count(*) > 1""", as_dict=True)
+	if items:
+		for item in items:
+			frappe.db.sql("""update `tabItem` set item_code=name where item_code = %s""", (item.item_code))
diff --git a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
index f5bd8c3..6f843cd 100644
--- a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
+++ b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
@@ -3,6 +3,10 @@
 from collections import defaultdict
 
 def execute():
+
+	frappe.reload_doc('stock', 'doctype', 'delivery_note_item', force=True)
+	frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item', force=True)
+
 	def map_rows(doc_row, return_doc_row, detail_field, doctype):
 		"""Map rows after identifying similar ones."""
 
diff --git a/erpnext/regional/address_template/templates/taiwan.html b/erpnext/regional/address_template/templates/taiwan.html
new file mode 100644
index 0000000..1715bea
--- /dev/null
+++ b/erpnext/regional/address_template/templates/taiwan.html
@@ -0,0 +1,4 @@
+{{ country }}<br>{% if pincode %}{{ pincode }}<br>{% endif -%}{{ county }}{{ city }}{{ address_line1 }}{% if address_line2 %}{{ address_line2 }}{% endif -%}
+{% if phone %}<br>Phone: {{ phone }}{% endif -%}
+{% if fax %}<br>Fax: {{ fax }}{% endif -%}
+{% if email_id %}<br>Email: {{ email_id }}{% endif -%}
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 9b7249e..a091ac7 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -7,7 +7,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.model.naming import make_autoname, revert_series_if_last
-from frappe.utils import flt, cint
+from frappe.utils import flt, cint, get_link_to_form
 from frappe.utils.jinja import render_template
 from frappe.utils.data import add_days
 from six import string_types
@@ -124,7 +124,7 @@
 		if has_expiry_date and not self.expiry_date:
 			frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \
 				.format(frappe.bold("Shelf Life in Days"),
-					frappe.utils.get_link_to_form("Item", self.item),
+					get_link_to_form("Item", self.item),
 					frappe.bold("Batch Expiry Date")),
 				title=_("Expiry Date Mandatory"))
 
@@ -264,16 +264,20 @@
 def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 	cond = ''
-	if serial_no:
+	if serial_no and frappe.get_cached_value('Item', item_code, 'has_batch_no'):
+		serial_nos = get_serial_nos(serial_no)
 		batch = frappe.get_all("Serial No",
 			fields = ["distinct batch_no"],
 			filters= {
 				"item_code": item_code,
 				"warehouse": warehouse,
-				"name": ("in", get_serial_nos(serial_no))
+				"name": ("in", serial_nos)
 			}
 		)
 
+		if not batch:
+			validate_serial_no_with_batch(serial_nos, item_code)
+
 		if batch and len(batch) > 1:
 			return []
 
@@ -288,4 +292,15 @@
 			and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
 		group by batch_id
 		order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
-	""".format(cond), (item_code, warehouse), as_dict=True)
\ No newline at end of file
+	""".format(cond), (item_code, warehouse), as_dict=True)
+
+def validate_serial_no_with_batch(serial_nos, item_code):
+	if frappe.get_cached_value("Serial No", serial_nos[0], "item_code") != item_code:
+		frappe.throw(_("The serial no {0} does not belong to item {1}")
+			.format(get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code)))
+
+	serial_no_link = ','.join([get_link_to_form("Serial No", sn) for sn in serial_nos])
+
+	message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
+	frappe.throw(_("There is no batch found against the {0}: {1}")
+		.format(message, serial_no_link))
\ No newline at end of file
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index eb4867d..cd86be3 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -177,7 +177,7 @@
 	return frappe.get_doc("Warehouse", args.docname).convert_to_group_or_ledger()
 
 def get_child_warehouses(warehouse):
-	lft, rgt = frappe.get_cached_value("Warehouse", warehouse, [lft, rgt])
+	lft, rgt = frappe.get_cached_value("Warehouse", warehouse, ["lft", "rgt"])
 
 	return frappe.db.sql_list("""select name from `tabWarehouse`
 		where lft >= %s and rgt <= %s""", (lft, rgt))