feat: Bank remittance report for tracking payroll-entry transactions (#17013)
* feat: Bank remittance report for tracking payroll-entry transactions
* feat: show Company bank account and bank name
* feat: Added Permissions to view report
* feat: Validated report only if debit account is present
* fix: query optimisation
* feat: Added From date and To date filters
* fix: code optimisation
* fix: requested changes
* fix: requested changes and refactor
* style: indentation
* style: Formatting
* fix: added parameter filters
diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json
index 562b999..d51684f 100644
--- a/erpnext/hr/doctype/payroll_entry/payroll_entry.json
+++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json
@@ -21,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break0",
"fieldtype": "Section Break",
"hidden": 0,
@@ -53,6 +54,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -86,6 +88,7 @@
"collapsible": 0,
"columns": 0,
"default": "Today",
+ "fetch_if_empty": 0,
"fieldname": "posting_date",
"fieldtype": "Date",
"hidden": 0,
@@ -120,6 +123,7 @@
"columns": 0,
"default": "",
"depends_on": "eval:doc.salary_slip_based_on_timesheet == 0",
+ "fetch_if_empty": 0,
"fieldname": "payroll_frequency",
"fieldtype": "Select",
"hidden": 0,
@@ -153,6 +157,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -186,6 +191,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
+ "fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -220,6 +226,7 @@
"collapsible": 0,
"collapsible_depends_on": "",
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
@@ -252,6 +259,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "branch",
"fieldtype": "Link",
"hidden": 0,
@@ -285,6 +293,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
@@ -318,6 +327,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
@@ -349,6 +359,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "designation",
"fieldtype": "Link",
"hidden": 0,
@@ -382,6 +393,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "number_of_employees",
"fieldtype": "Int",
"hidden": 0,
@@ -414,6 +426,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "sec_break20",
"fieldtype": "Section Break",
"hidden": 0,
@@ -445,6 +458,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "employees",
"fieldtype": "Table",
"hidden": 0,
@@ -478,6 +492,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break_13",
"fieldtype": "Section Break",
"hidden": 0,
@@ -509,6 +524,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "validate_attendance",
"fieldtype": "Check",
"hidden": 0,
@@ -541,6 +557,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "attendance_detail_html",
"fieldtype": "HTML",
"hidden": 0,
@@ -572,6 +589,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break_12",
"fieldtype": "Section Break",
"hidden": 0,
@@ -604,6 +622,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
+ "fetch_if_empty": 0,
"fieldname": "salary_slip_based_on_timesheet",
"fieldtype": "Check",
"hidden": 0,
@@ -637,6 +656,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "select_payroll_period",
"fieldtype": "Section Break",
"hidden": 0,
@@ -670,6 +690,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
+ "fetch_if_empty": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -703,6 +724,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
+ "fetch_if_empty": 0,
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -735,6 +757,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"hidden": 0,
@@ -766,6 +789,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "deduct_tax_for_unclaimed_employee_benefits",
"fieldtype": "Check",
"hidden": 0,
@@ -798,6 +822,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "deduct_tax_for_unsubmitted_tax_exemption_proof",
"fieldtype": "Check",
"hidden": 0,
@@ -830,6 +855,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"hidden": 0,
@@ -864,6 +890,7 @@
"columns": 0,
"default": ":Company",
"fetch_from": "",
+ "fetch_if_empty": 0,
"fieldname": "cost_center",
"fieldtype": "Link",
"hidden": 0,
@@ -897,6 +924,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_18",
"fieldtype": "Column Break",
"hidden": 0,
@@ -928,6 +956,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "project",
"fieldtype": "Link",
"hidden": 0,
@@ -961,6 +990,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -993,6 +1023,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "account",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1026,6 +1057,7 @@
"collapsible": 0,
"columns": 0,
"description": "Select Payment Account to make Bank Entry",
+ "fetch_if_empty": 0,
"fieldname": "payment_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1059,6 +1091,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "section_break2",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1090,6 +1123,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1122,6 +1156,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "salary_slips_created",
"fieldtype": "Check",
"hidden": 1,
@@ -1154,6 +1189,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "salary_slips_submitted",
"fieldtype": "Check",
"hidden": 1,
@@ -1181,17 +1217,16 @@
}
],
"has_web_view": 0,
- "hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-cog",
"idx": 0,
- "image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2019-02-05 10:41:08.865842",
+ "menu_index": 0,
+ "modified": "2019-03-26 16:55:04.158800",
"modified_by": "Administrator",
"module": "HR",
"name": "Payroll Entry",
@@ -1210,7 +1245,7 @@
"permlevel": 0,
"print": 0,
"read": 1,
- "report": 0,
+ "report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
@@ -1220,7 +1255,6 @@
],
"quick_entry": 0,
"read_only": 0,
- "read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
diff --git a/erpnext/hr/report/bank_remittance/__init__.py b/erpnext/hr/report/bank_remittance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/report/bank_remittance/__init__.py
diff --git a/erpnext/hr/report/bank_remittance/bank_remittance.js b/erpnext/hr/report/bank_remittance/bank_remittance.js
new file mode 100644
index 0000000..1e10f24
--- /dev/null
+++ b/erpnext/hr/report/bank_remittance/bank_remittance.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Bank Remittance"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ fieldname:"from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ },
+ {
+ fieldname:"to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ },
+
+ ]
+}
+
diff --git a/erpnext/hr/report/bank_remittance/bank_remittance.json b/erpnext/hr/report/bank_remittance/bank_remittance.json
new file mode 100644
index 0000000..5a6228e
--- /dev/null
+++ b/erpnext/hr/report/bank_remittance/bank_remittance.json
@@ -0,0 +1,25 @@
+{
+ "add_total_row": 0,
+ "creation": "2019-03-26 16:57:52.558895",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2019-03-26 16:57:52.558895",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Bank Remittance",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Payroll Entry",
+ "report_name": "Bank Remittance",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "HR Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/hr/report/bank_remittance/bank_remittance.py b/erpnext/hr/report/bank_remittance/bank_remittance.py
new file mode 100644
index 0000000..b2d2c53
--- /dev/null
+++ b/erpnext/hr/report/bank_remittance/bank_remittance.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import formatdate
+import itertools
+from frappe import _, get_all
+
+def execute(filters=None):
+ columns = [
+ {
+ "label": _("Payroll Number"),
+ "fieldtype": "Link",
+ "fieldname": "payroll_no",
+ "options": "Payroll Entry",
+ "width": 150
+ },
+ {
+ "label": _("Debit A/C Number"),
+ "fieldtype": "Int",
+ "fieldname": "debit_account",
+ "hidden": 1,
+ "width": 200
+ },
+ {
+ "label": _("Payment Date"),
+ "fieldtype": "Data",
+ "fieldname": "payment_date",
+ "width": 100
+ },
+ {
+ "label": _("Employee Name"),
+ "fieldtype": "Link",
+ "fieldname": "employee_name",
+ "options": "Employee",
+ "width": 200
+ },
+ {
+ "label": _("Bank Name"),
+ "fieldtype": "Data",
+ "fieldname": "bank_name",
+ "width": 50
+ },
+ {
+ "label": _("Employee A/C Number"),
+ "fieldtype": "Int",
+ "fieldname": "employee_account_no",
+ "width": 50
+ },
+ {
+ "label": _("IFSC Code"),
+ "fieldtype": "Data",
+ "fieldname": "bank_code",
+ "width": 100
+ },
+ {
+ "label": _("Currency"),
+ "fieldtype": "Data",
+ "fieldname": "currency",
+ "width": 50
+ },
+ {
+ "label": _("Net Salary Amount"),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "fieldname": "amount",
+ "width": 100
+ }
+ ]
+ data = []
+
+ accounts = get_bank_accounts()
+ payroll_entries = get_payroll_entries(accounts, filters)
+ salary_slips = get_salary_slips(payroll_entries)
+ get_emp_bank_ifsc_code(salary_slips)
+
+ for salary in salary_slips:
+ if salary.bank_name and salary.bank_account_no and salary.debit_acc_no and salary.status in ["Submitted", "Paid"]:
+ row = {
+ "payroll_no": salary.payroll_entry,
+ "debit_account": salary.debit_acc_no,
+ "payment_date": frappe.utils.formatdate(salary.modified.strftime('%Y-%m-%d')),
+ "bank_name": salary.bank_name,
+ "employee_account_no": salary.bank_account_no,
+ "bank_code": salary.ifsc_code,
+ "employee_name": salary.employee+": " + salary.employee_name,
+ "currency": frappe.get_cached_value('Company', filters.company, 'default_currency'),
+ "amount": salary.net_pay,
+ }
+ data.append(row)
+ return columns, data
+
+def get_bank_accounts():
+ accounts = [d.name for d in get_all("Account", filters={"account_type": "Bank"})]
+ return accounts
+
+def get_payroll_entries(accounts, filters):
+ payroll_filter = [
+ ('payment_account', 'IN', accounts),
+ ('number_of_employees', '>', 0),
+ ('Company', '=', filters.company)
+ ]
+ if filters.to_date:
+ payroll_filter.append(('posting_date', '<', filters.to_date))
+
+ if filters.from_date:
+ payroll_filter.append(('posting_date', '>', filters.from_date))
+
+ entries = get_all("Payroll Entry", payroll_filter, ["name", "payment_account"])
+
+ payment_accounts = [d.payment_account for d in entries]
+ set_company_account(payment_accounts, entries)
+ return entries
+
+def get_salary_slips(payroll_entries):
+ payroll = [d.name for d in payroll_entries]
+ salary_slips = get_all("Salary Slip", filters = [("payroll_entry", "IN", payroll)],
+ fields = ["modified", "net_pay", "bank_name", "bank_account_no", "payroll_entry", "employee", "employee_name", "status"]
+ )
+
+ payroll_entry_map = {}
+ for entry in payroll_entries:
+ payroll_entry_map[entry.name] = entry
+
+ # appending company debit accounts
+ for slip in salary_slips:
+ slip["debit_acc_no"] = payroll_entry_map[slip.payroll_entry]['company_account']
+
+ return salary_slips
+
+def get_emp_bank_ifsc_code(salary_slips):
+ emp_names = [d.employee for d in salary_slips]
+ ifsc_codes = get_all("Employee", [("name", "IN", emp_names)], ["ifsc_code", "name"])
+
+ ifsc_codes_map = {}
+ for code in ifsc_codes:
+ ifsc_codes_map[code.name] = code
+
+ for slip in salary_slips:
+ slip["ifsc_code"] = ifsc_codes_map[code.name]['ifsc_code']
+
+ return salary_slips
+
+def set_company_account(payment_accounts, payroll_entries):
+ company_accounts = get_all("Bank Account", [("account", "in", payment_accounts)], ["account", "bank_account_no"])
+ company_accounts_map = {}
+ for acc in company_accounts:
+ company_accounts_map[acc.account] = acc
+
+ for entry in payroll_entries:
+ entry["company_account"] = company_accounts_map[entry.payment_account]['bank_account_no']
+
+ return payroll_entries