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):