Merge pull request #17057 from hrwX/bank_reconciliation_fix

fix(Bank Reconciliation): get credit and debit as float and not string
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index ac74b45..3d9604d 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -112,6 +112,8 @@
 				["company", "name"], as_dict=True):
 				acc_name_map[d["company"]] = d["name"]
 
+			if not acc_name_map: return
+
 			for company in descendants:
 				doc = frappe.copy_doc(self)
 				doc.flags.ignore_root_company_validation = True
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index c1ac407..2fac232 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -241,8 +241,7 @@
 		"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
 		"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
 		"erpnext.projects.doctype.project.project.hourly_reminder",
-		"erpnext.projects.doctype.project.project.collect_project_status",
-		"erpnext.support.doctype.issue.issue.update_support_timer",
+		"erpnext.projects.doctype.project.project.collect_project_status"
 	],
 	"daily": [
 		"erpnext.stock.reorder_item.reorder_item",
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 3fd266b..684f348 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -441,7 +441,7 @@
 	def calculate_net_pay(self):
 		if self.salary_structure:
 			self.calculate_component_amounts()
-		
+
 		disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
 		precision = frappe.defaults.get_global_default("currency_precision")
 		self.total_deduction = 0
@@ -452,10 +452,10 @@
 
 		self.set_loan_repayment()
 
-		self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
+		self.net_pay = (flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))) * flt(self.payment_days / self.total_working_days)
 		self.rounded_total = rounded(self.net_pay,
 			self.precision("net_pay") if disable_rounded_total else 0)
-		
+
 		if self.net_pay < 0:
 			frappe.throw(_("Net Pay cannnot be negative"))
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 238fe8b..7bdde54 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -594,4 +594,4 @@
 erpnext.patches.v12_0.move_target_distribution_from_parent_to_child
 erpnext.patches.v12_0.stock_entry_enhancements
 erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
-
+erpnext.patches.v12_0.move_item_tax_to_item_tax_template
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 845d3a0..2686e58 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -165,6 +165,13 @@
 
 		self.update_nsm_model()
 
+	def update_status(self):
+		if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
+			from datetime import datetime
+			if self.exp_end_date < datetime.now().date():
+				self.db_set('status', 'Overdue')
+				self.update_project()
+
 @frappe.whitelist()
 def check_if_child_exists(name):
 	child_tasks = frappe.get_all("Task", filters={"parent_task": name})
@@ -196,10 +203,9 @@
 		task.save()
 
 def set_tasks_as_overdue():
-	frappe.db.sql("""update tabTask set `status`='Overdue'
-		where exp_end_date is not null
-		and exp_end_date < CURDATE()
-		and `status` not in ('Completed', 'Cancelled')""")
+	tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Completed']]})
+	for task in tasks:
+		frappe.get_doc("Task", task.name).update_status()
 
 @frappe.whitelist()
 def get_children(doctype, parent, task=None, project=None, is_root=False):
diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js
index 9246aa6..9682768 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.js
+++ b/erpnext/regional/report/gstr_1/gstr_1.js
@@ -4,7 +4,7 @@
 frappe.query_reports["GSTR-1"] = {
 	"filters": [
 		{
-			"fieldname":"company",
+			"fieldname": "company",
 			"label": __("Company"),
 			"fieldtype": "Link",
 			"options": "Company",
@@ -12,22 +12,22 @@
 			"default": frappe.defaults.get_user_default("Company")
 		},
 		{
-			"fieldname":"company_address",
+			"fieldname": "company_address",
 			"label": __("Address"),
 			"fieldtype": "Link",
 			"options": "Address",
-			"get_query": function() {
+			"get_query": function () {
 				var company = frappe.query_report.get_filter_value('company');
 				if (company) {
 					return {
 						"query": 'frappe.contacts.doctype.address.address.address_query',
-						"filters": { link_doctype: 'Company', link_name: company}
+						"filters": { link_doctype: 'Company', link_name: company }
 					};
 				}
 			}
 		},
 		{
-			"fieldname":"from_date",
+			"fieldname": "from_date",
 			"label": __("From Date"),
 			"fieldtype": "Date",
 			"reqd": 1,
@@ -35,19 +35,34 @@
 			"width": "80"
 		},
 		{
-			"fieldname":"to_date",
+			"fieldname": "to_date",
 			"label": __("To Date"),
 			"fieldtype": "Date",
 			"reqd": 1,
 			"default": frappe.datetime.get_today()
 		},
 		{
-			"fieldname":"type_of_business",
+			"fieldname": "type_of_business",
 			"label": __("Type of Business"),
 			"fieldtype": "Select",
 			"reqd": 1,
-			"options": ["B2B", "B2C Large", "B2C Small","CDNR", "EXPORT"],
+			"options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"],
 			"default": "B2B"
 		}
-	]
+	],
+	onload: function (report) {
+
+		report.page.add_inner_button(__("Download as Json"), function () {
+			var filters = report.get_values();
+
+			const args = {
+				cmd: 'erpnext.regional.report.gstr_1.gstr_1.get_json',
+				data: report.data,
+				report_name: report.report_name,
+				filters: filters
+			};
+
+			open_url_post(frappe.request.url, args);
+		});
+	}
 }
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index b01abce..24bd6cf 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -4,9 +4,10 @@
 from __future__ import unicode_literals
 import frappe, json
 from frappe import _
-from frappe.utils import flt, formatdate
+from frappe.utils import flt, formatdate, now_datetime, getdate
 from datetime import date
 from six import iteritems
+from erpnext.regional.doctype.gstr_3b_report.gstr_3b_report import get_period
 
 def execute(filters=None):
 	return Gstr1Report(filters).run()
@@ -38,7 +39,7 @@
 			shipping_bill_date,
 			reason_for_issuing_document
 		"""
-		self.customer_type = "Company" if self.filters.get("type_of_business") ==  "B2B" else "Individual"
+		# self.customer_type = "Company" if self.filters.get("type_of_business") ==  "B2B" else "Individual"
 
 	def run(self):
 		self.get_columns()
@@ -113,9 +114,14 @@
 				if self.filters.get(opts[0]):
 					conditions += opts[1]
 
-		customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type})
+		# customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type})
 
 		if self.filters.get("type_of_business") ==  "B2B":
+			customers = frappe.get_all("Customer",
+				filters={
+					"gst_category": ["in", ["Registered Regular", "Deemed Export", "SEZ"]]
+			})
+
 			conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1
 				and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers]))
 
@@ -124,6 +130,11 @@
 			if not b2c_limit:
 				frappe.throw(_("Please set B2C Limit in GST Settings."))
 
+		customers = frappe.get_all("Customer",
+			filters={
+				"gst_category": ["in", ["Unregistered"]]
+		})
+
 		if self.filters.get("type_of_business") ==  "B2C Large":
 			conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
 				and grand_total > {0} and is_return != 1 and customer in ({1})""".\
@@ -494,3 +505,158 @@
 				}
 			]
 		self.columns = self.invoice_columns + self.tax_columns + self.other_columns
+
+@frappe.whitelist()
+def get_json():
+	data = frappe._dict(frappe.local.form_dict)
+
+	del data["cmd"]
+	if "csrf_token" in data:
+		del data["csrf_token"]
+
+	filters = json.loads(data["filters"])
+	report_data = json.loads(data["data"])
+	report_name = data["report_name"]
+	gstin = get_company_gstin_number(filters["company"])
+
+	fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
+
+	gst_json = {"gstin": "", "version": "GST2.2.9",
+		"hash": "hash", "gstin": gstin, "fp": fp}
+
+	res = {}
+	if filters["type_of_business"] == "B2B":
+		for item in report_data:
+			res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
+
+		out = get_b2b_json(res, gstin)
+		gst_json["b2b"] = out
+	elif filters["type_of_business"] == "B2C Large":
+		for item in report_data:
+			res.setdefault(item["place_of_supply"], []).append(item)
+
+		out = get_b2cl_json(res, gstin)
+		gst_json["b2cl"] = out
+	elif filters["type_of_business"] == "EXPORT":
+		for item in report_data:
+			res.setdefault(item["export_type"], []).append(item)
+
+		out = get_export_json(res)
+		gst_json["exp"] = out
+
+	download_json_file(report_name, filters["type_of_business"], gst_json)
+
+def get_b2b_json(res, gstin):
+	inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, []
+	for gst_in in res:
+		b2b_item, inv = {"ctin": gst_in, "inv": []}, []
+		if not gst_in: continue
+
+		for number, invoice in iteritems(res[gst_in]):
+			inv_item = get_basic_invoice_detail(invoice[0])
+			inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0])
+			inv_item["rchrg"] = invoice[0]["reverse_charge"]
+			inv_item["inv_typ"] = inv_type.get(invoice[0].get("gst_category", ""),"")
+
+			if inv_item["pos"]=="00": continue
+			inv_item["itms"] = []
+
+			for item in invoice:
+				inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
+
+			inv.append(inv_item)
+
+		if not inv: continue
+		b2b_item["inv"] = inv
+		out.append(b2b_item)
+
+	return out
+
+def get_b2cl_json(res, gstin):
+	out = []
+	for pos in res:
+		b2cl_item, inv = {"pos": "%02d" % int(pos.split('-')[0]), "inv": []}, []
+
+		for row in res[pos]:
+			inv_item = get_basic_invoice_detail(row)
+			if row.get("sale_from_bonded_wh"):
+				inv_item["inv_typ"] = "CBW"
+
+			inv_item["itms"] = [get_rate_and_tax_details(row, gstin)]
+
+			inv.append(inv_item)
+
+		b2cl_item["inv"] = inv
+		out.append(b2cl_item)
+
+	return out
+
+def get_export_json(res):
+	out = []
+	for exp_type in res:
+		exp_item, inv = {"exp_typ": exp_type, "inv": []}, []
+
+		for row in res[exp_type]:
+			inv_item = get_basic_invoice_detail(row)
+			inv_item["itms"] = [{
+				"txval": flt(row["taxable_value"], 2),
+				"rt": row["rate"] or 0,
+				"iamt": 0,
+				"csamt": 0
+			}]
+
+			inv.append(inv_item)
+
+		exp_item["inv"] = inv
+		out.append(exp_item)
+
+	return out
+
+def get_basic_invoice_detail(row):
+	return {
+		"inum": row["invoice_number"],
+		"idt": getdate(row["posting_date"]).strftime('%d-%m-%Y'),
+		"val": flt(row["invoice_value"], 2)
+	}
+
+def get_rate_and_tax_details(row, gstin):
+	itm_det = {"txval": flt(row["taxable_value"], 2),
+		"rt": row["rate"],
+		"csamt": (flt(row.get("cess_amount"), 2) or 0)
+	}
+
+	# calculate rate
+	num = 1 if not row["rate"] else "%d%02d" % (row["rate"], 1)
+	rate = row.get("rate") or 0
+
+	# calculate tax amount added
+	tax = flt((row["taxable_value"]*rate)/100.0, 2)
+	frappe.errprint([tax, tax/2])
+	if row.get("customer_gstin") and gstin[0:2] == row["customer_gstin"][0:2]:
+		itm_det.update({"camt": flt(tax/2.0, 2), "samt": flt(tax/2.0, 2)})
+	else:
+		itm_det.update({"iamt": tax})
+
+	return {"num": int(num), "itm_det": itm_det}
+
+def get_company_gstin_number(company):
+	filters = [
+		["is_your_company_address", "=", 1],
+		["Dynamic Link", "link_doctype", "=", "Company"],
+		["Dynamic Link", "link_name", "=", company],
+		["Dynamic Link", "parenttype", "=", "Address"],
+	]
+
+	gstin = frappe.get_all("Address", filters=filters, fields=["gstin"])
+
+	if gstin:
+		return gstin[0]["gstin"]
+	else:
+		frappe.throw(_("No GST No. found for the Company."))
+
+def download_json_file(filename, report_type, data):
+	''' download json content in a file '''
+	frappe.response['filename'] = frappe.scrub("{0} {1}".format(filename, report_type)) + '.json'
+	frappe.response['filecontent'] = json.dumps(data)
+	frappe.response['content_type'] = 'application/json'
+	frappe.response['type'] = 'download'
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index e690429..2bee844 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -65,10 +65,20 @@
 			self.first_responded_on = now()
 		if self.status=="Closed" and status !="Closed":
 			self.resolution_date = now()
+			self.update_agreement_status()
 		if self.status=="Open" and status !="Open":
 			# if no date, it should be set as None and not a blank string "", as per mysql strict config
 			self.resolution_date = None
 
+	def update_agreement_status(self):
+		current_time = frappe.flags.current_time or now_datetime()
+		if self.service_level_agreement:
+			if (round(time_diff_in_hours(self.response_by, current_time), 2) < 0
+				or round(time_diff_in_hours(self.resolution_by, current_time), 2) < 0):
+				self.agreement_status = "Failed"
+			else:
+				self.agreement_status = "Fulfilled"
+
 	def create_communication(self):
 		communication = frappe.new_doc("Communication")
 		communication.update({
@@ -265,18 +275,6 @@
 	"""Called when Contact is deleted"""
 	frappe.db.sql("""UPDATE `tabIssue` set contact='' where contact=%s""", contact.name)
 
-def update_support_timer():
-	issues = frappe.get_list("Issue", filters={"status": "Open"}, order_by="creation DESC")
-	for issue in issues:
-		issue = frappe.get_doc("Issue", issue.name)
-
-		if round(time_diff_in_hours(issue.response_by, now_datetime()), 2) < 0 or round(time_diff_in_hours(issue.resolution_by, now_datetime()), 2) < 0:
-			issue.agreement_status = "Failed"
-		else:
-			issue.agreement_status = "Fulfilled"
-		issue.save()
-
-
 def get_holidays(holiday_list_name):
 	holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
 	holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 4675874..2cd7601 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -35,6 +35,24 @@
 		self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 18, 0))
 		self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
 
+		frappe.flags.current_time = datetime.datetime(2019, 3, 3, 12, 0)
+
+		issue.status = 'Closed'
+		issue.save()
+
+		self.assertEqual(issue.agreement_status, 'Fulfilled')
+
+		issue.status = 'Open'
+		issue.save()
+
+		frappe.flags.current_time = datetime.datetime(2019, 3, 5, 12, 0)
+
+		issue.status = 'Closed'
+		issue.save()
+
+		self.assertEqual(issue.agreement_status, 'Failed')
+
+
 
 def make_issue(creation, customer=None):