Merge pull request #15523 from rohitwaghchaure/fix_attendance_tool_issue

[Fix] Attendance tool
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 7b247f6..68f1fa7 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__ = '10.1.52'
+__version__ = '10.1.54'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index a515724..93cabb0 100644
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -506,7 +506,7 @@
 			frappe.db.commit()
 			name_list.append(name)
 	except Exception:
-		frappe.log_error(frappe.get_traceback())
 		frappe.db.rollback()
+		frappe.log_error(frappe.get_traceback())
 
 	return name_list
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index 5497384..0e2742d 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -3,47 +3,75 @@
 
 from __future__ import unicode_literals
 import frappe
+from frappe.utils import flt
 from frappe import _
 
 def execute(filters=None):
 	columns, data = get_columns(), get_data(filters)
 	return columns, data
-	
+
 def get_data(filters):
-	data = frappe.db.sql("""
-		select 
-			a.name as asset, a.asset_category, a.status, 
-			a.depreciation_method, a.purchase_date, a.gross_purchase_amount,
-			ds.schedule_date as depreciation_date, ds.depreciation_amount, 
-			ds.accumulated_depreciation_amount, 
-			(a.gross_purchase_amount - ds.accumulated_depreciation_amount) as amount_after_depreciation,
-			ds.journal_entry as depreciation_entry
-		from
-			`tabAsset` a, `tabDepreciation Schedule` ds
-		where
-			a.name = ds.parent
-			and a.docstatus=1
-			and ifnull(ds.journal_entry, '') != ''
-			and ds.schedule_date between %(from_date)s and %(to_date)s
-			and a.company = %(company)s
-			{conditions}
-		order by
-			a.name asc, ds.schedule_date asc
-	""".format(conditions=get_filter_conditions(filters)), filters, as_dict=1)
-		
-	return data
-	
-def get_filter_conditions(filters):
-	conditions = ""
-	
+	data = []
+	depreciation_accounts = frappe.db.sql_list(""" select name from tabAccount
+		where ifnull(account_type, '') = 'Depreciation' """)
+
+	filters_data = [["company", "=", filters.get('company')],
+		["posting_date", ">=", filters.get('from_date')],
+		["posting_date", "<=", filters.get('to_date')],
+		["against_voucher_type", "=", "Asset"],
+		["account", "in", depreciation_accounts]]
+
 	if filters.get("asset"):
-		conditions += " and a.name = %(asset)s"
-	
+		filters_data.append(["against_voucher", "=", filters.get("asset")])
+
 	if filters.get("asset_category"):
-		conditions += " and a.asset_category = %(asset_category)s"
-		
-	return conditions
+		assets = frappe.db.sql_list("""select name from tabAsset
+			where asset_category = %s and docstatus=1""", filters.get("asset_category"))
+
+		filters_data.append(["against_voucher", "in", assets])
+
+	gl_entries = frappe.get_all('GL Entry',
+		filters= filters_data,
+		fields = ["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"],
+		order_by= "against_voucher, posting_date")
+
+	if not gl_entries:
+		return data
+
+	assets = [d.against_voucher for d in gl_entries]
+	assets_details = get_assets_details(assets)
 	
+	for d in gl_entries:
+		asset_data = assets_details.get(d.against_voucher)
+		if not asset_data.get("accumulated_depreciation_amount"):
+			asset_data.accumulated_depreciation_amount = d.debit
+		else:
+			asset_data.accumulated_depreciation_amount += d.debit
+
+		row = frappe._dict(asset_data)
+		row.update({
+			"depreciation_amount": d.debit,
+			"depreciation_date": d.posting_date,
+			"amount_after_depreciation": (flt(row.gross_purchase_amount) -
+				flt(row.accumulated_depreciation_amount)),
+			"depreciation_entry": d.voucher_no
+		})
+
+		data.append(row)
+
+	return data
+
+def get_assets_details(assets):
+	assets_details = {}
+
+	fields = ["name as asset", "gross_purchase_amount",
+		"asset_category", "status", "depreciation_method", "purchase_date"]
+
+	for d in frappe.get_all("Asset", fields = fields, filters = {'name': ('in', assets)}):
+		assets_details.setdefault(d.asset, d)
+
+	return assets_details
+
 def get_columns():
 	return [
 		{
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 01577eb..875df59 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -67,11 +67,33 @@
 					frm.trigger("create_asset_maintenance");
 				}, __("Make"));
 			}
+
+			if (!frm.doc.calculate_depreciation) {
+				frm.add_custom_button(__("Depreciation Entry"), function() {
+					frm.trigger("make_journal_entry");
+				}, __("Make"));
+			}
+
 			frm.page.set_inner_btn_group_as_primary(__("Make"));
 			frm.trigger("setup_chart");
 		}
 	},
 
+	make_journal_entry: function(frm) {
+		frappe.call({
+			method: "erpnext.assets.doctype.asset.asset.make_journal_entry",
+			args: {
+				asset_name: frm.doc.name
+			},
+			callback: function(r) {
+				if (r.message) {
+					var doclist = frappe.model.sync(r.message);
+					frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+				}
+			}
+		})
+	},
+
 	setup_chart: function(frm) {
 		var x_intervals = [frm.doc.purchase_date];
 		var asset_values = [frm.doc.gross_purchase_amount];
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 8bba0b6..7fa5810 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -283,3 +283,34 @@
 	})
 
 	return ret
+
+@frappe.whitelist()
+def make_journal_entry(asset_name):
+	asset = frappe.get_doc("Asset", asset_name)
+	fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \
+		get_depreciation_accounts(asset)
+
+	depreciation_cost_center, depreciation_series = frappe.db.get_value("Company", asset.company,
+		["depreciation_cost_center", "series_for_depreciation_entry"])
+	depreciation_cost_center = asset.cost_center or depreciation_cost_center
+
+	je = frappe.new_doc("Journal Entry")
+	je.voucher_type = "Depreciation Entry"
+	je.naming_series = depreciation_series
+	je.company = asset.company
+	je.remark = "Depreciation Entry against asset {0}".format(asset_name)
+
+	je.append("accounts", {
+		"account": depreciation_expense_account,
+		"reference_type": "Asset",
+		"reference_name": asset.name,
+		"cost_center": depreciation_cost_center
+	})
+
+	je.append("accounts", {
+		"account": accumulated_depreciation_account,
+		"reference_type": "Asset",
+		"reference_name": asset.name
+	})
+
+	return je
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index acd5892..89d3808 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -5,13 +5,13 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import flt, today, getdate
+from frappe.utils import flt, today, getdate, cint
 
 def post_depreciation_entries(date=None):
 	# Return if automatic booking of asset depreciation is disabled
-	if not frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically"):
+	if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")):
 		return
-		
+
 	if not date:
 		date = today()
 	for asset in get_depreciable_assets(date):
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 5e95b9e..8d6d070 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -139,7 +139,7 @@
 					.format(args.item_code), StockOverReturnError)
 			elif abs(current_stock_qty) > max_returnable_qty:
 				frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
-					.format(args.idx, reference_qty, args.item_code), StockOverReturnError)
+					.format(args.idx, max_returnable_qty, args.item_code), StockOverReturnError)
 
 def get_ref_item_dict(valid_items, ref_item_row):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js
index afb7f1a..8cecbaa 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.js
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.js
@@ -113,16 +113,14 @@
 // Get leave details
 //---------------------------------------------------------------------
 var get_emp_and_leave_details = function(doc, dt, dn) {
-	if(!doc.start_date){
-		return frappe.call({
-			method: 'get_emp_and_leave_details',
-			doc: locals[dt][dn],
-			callback: function(r, rt) {
-				cur_frm.refresh();
-				calculate_all(doc, dt, dn);
-			}
-		});
-	}
+	return frappe.call({
+		method: 'get_emp_and_leave_details',
+		doc: locals[dt][dn],
+		callback: function(r, rt) {
+			cur_frm.refresh();
+			calculate_all(doc, dt, dn);
+		}
+	});
 }
 
 cur_frm.cscript.employee = function(doc,dt,dn){
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 5f195d9..3ee98e6 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -181,7 +181,8 @@
 			if len(st_name) > 1:
 				frappe.msgprint(_("Multiple active Salary Structures found for employee {0} for the given dates")
 					.format(self.employee), title=_('Warning'))
-			return st_name and st_name[0][0] or ''
+			self.salary_structure = st_name and st_name[0][0] or ''
+			return self.salary_structure
 		else:
 			self.salary_structure = None
 			frappe.msgprint(_("No active or default Salary Structure found for employee {0} for the given dates")
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index c91bb8f..3f6cb44 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -16,16 +16,23 @@
 		self.update_new_bom()
 		bom_list = self.get_parent_boms(self.new_bom)
 		updated_bom = []
+
 		for bom in bom_list:
-			bom_obj = frappe.get_doc("BOM", bom)
-			bom_obj.get_doc_before_save()
-			updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
-			bom_obj.calculate_cost()
-			bom_obj.update_parent_cost()
-			bom_obj.db_update()
-			if (getattr(bom_obj.meta, 'track_changes', False)
-				and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
-				bom_obj.save_version()
+			try:
+				bom_obj = frappe.get_doc("BOM", bom)
+				bom_obj.get_doc_before_save()
+				updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
+				bom_obj.calculate_cost()
+				bom_obj.update_parent_cost()
+				bom_obj.db_update()
+				if (getattr(bom_obj.meta, 'track_changes', False)
+					and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
+					bom_obj.save_version()
+
+				frappe.db.commit()
+			except Exception:
+				frappe.db.rollback()
+				frappe.log_error(frappe.get_traceback())
 
 	def validate_bom(self):
 		if cstr(self.current_bom) == cstr(self.new_bom):
diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js
index 6fa710d..484d81d 100644
--- a/erpnext/public/js/setup_wizard.js
+++ b/erpnext/public/js/setup_wizard.js
@@ -138,10 +138,15 @@
 
 		validate: function () {
 			// validate fiscal year start and end dates
-			if (this.values.fy_start_date == 'Invalid date' || this.values.fy_end_date == 'Invalid date') {
+			const invalid = this.values.fy_start_date == 'Invalid date' ||
+				this.values.fy_end_date == 'Invalid date';
+			const start_greater_than_end = this.values.fy_start_date > this.values.fy_end_date;
+
+			if (invalid || start_greater_than_end) {
 				frappe.msgprint(__("Please enter valid Financial Year Start and End Dates"));
 				return false;
 			}
+
 			return true;
 		},
 
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
index 9ade1af..7311025 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
@@ -6,7 +6,8 @@
 		'non_standard_fieldnames': {
 			'Purchase Invoice': 'purchase_receipt',
 			'Landed Cost Voucher': 'receipt_document',
-			'Subscription': 'reference_document'
+			'Subscription': 'reference_document',
+			'Purchase Receipt': 'return_against'
 		},
 		'internal_links': {
 			'Purchase Order': ['items', 'purchase_order'],
@@ -24,7 +25,7 @@
 			},
 			{
 				'label': _('Returns'),
-				'items': ['Stock Entry']
+				'items': ['Purchase Receipt']
 			},
 			{
 				'label': _('Subscription'),
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index a8cab80..3d1979d 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -187,13 +187,14 @@
 	update_serial_nos(sle, item_det)
 
 def validate_serial_no(sle, item_det):
+	serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else []
+
 	if item_det.has_serial_no==0:
-		if sle.serial_no:
+		if serial_nos:
 			frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code),
 				SerialNoNotRequiredError)
 	elif sle.is_cancelled == "No":
-		if sle.serial_no:
-			serial_nos = get_serial_nos(sle.serial_no)
+		if serial_nos:
 			if cint(sle.actual_qty) != flt(sle.actual_qty):
 				frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty))
 
@@ -239,6 +240,12 @@
 		elif sle.actual_qty < 0 or not item_det.serial_no_series:
 			frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
 				SerialNoRequiredError)
+	elif serial_nos:
+		for serial_no in serial_nos:
+			sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1)
+			if sr and sle.actual_qty < 0 and sr.warehouse != sle.warehouse:
+				frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
+					.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
 
 def has_duplicate_serial_no(sn, sle):
 	if sn.warehouse: