From 4f135c9805744d5b2e1a08750f61b4ddbebfef08 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 30 Jul 2018 16:31:34 +0530 Subject: [PATCH] Payroll entry ux improvements and processing via background jobs --- .../additional_salary/additional_salary.json | 156 ++++++----- .../additional_salary/additional_salary.py | 8 +- .../hr/doctype/payroll_entry/payroll_entry.js | 116 ++++---- .../doctype/payroll_entry/payroll_entry.json | 248 ++++++++++++------ .../hr/doctype/payroll_entry/payroll_entry.py | 129 ++++----- .../payroll_entry/payroll_entry_dashboard.py | 20 ++ erpnext/hr/doctype/salary_slip/salary_slip.py | 17 +- 7 files changed, 413 insertions(+), 281 deletions(-) create mode 100644 erpnext/hr/doctype/payroll_entry/payroll_entry_dashboard.py diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.json b/erpnext/hr/doctype/additional_salary/additional_salary.json index b29b30b724..4bf2343d34 100644 --- a/erpnext/hr/doctype/additional_salary/additional_salary.json +++ b/erpnext/hr/doctype/additional_salary/additional_salary.json @@ -46,39 +46,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -153,28 +120,29 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "salary_component", + "fetch_from": "employee.department", + "fieldname": "department", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Salary Component", + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Department", "length": 0, "no_copy": 0, - "options": "Salary Component", + "options": "Department", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, - "search_index": 1, + "reqd": 0, + "search_index": 0, "set_only_once": 0, "translatable": 0, "unique": 0 @@ -186,29 +154,27 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "salary_component.type", - "fieldname": "type", - "fieldtype": "Data", + "fieldname": "payroll_date", + "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, - "label": "Type", + "label": "Payroll Date", "length": 0, "no_copy": 0, - "options": "", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, - "search_index": 0, + "reqd": 1, + "search_index": 1, "set_only_once": 0, "translatable": 0, "unique": 0 @@ -251,18 +217,19 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "payroll_date", - "fieldtype": "Date", + "fieldname": "salary_component", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, - "label": "Payroll Date", + "in_standard_filter": 1, + "label": "Salary Component", "length": 0, "no_copy": 0, + "options": "Salary Component", "permlevel": 0, "precision": "", "print_hide": 0, @@ -315,8 +282,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", + "fieldname": "company", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -325,18 +291,18 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Department", + "label": "Company", "length": 0, "no_copy": 0, - "options": "Department", + "options": "Company", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -375,6 +341,72 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "salary_component.type", + "fieldname": "type", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Salary Component Type", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "overwrite_salary_structure_amount", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Overwrite Salary Structure Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -418,7 +450,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-07-28 17:50:25.725444", + "modified": "2018-07-30 16:02:01.538750", "modified_by": "Administrator", "module": "HR", "name": "Additional Salary", @@ -474,4 +506,4 @@ "track_changes": 1, "track_seen": 0, "track_views": 0 -} +} \ No newline at end of file diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py index b54d726f16..6f87954f50 100644 --- a/erpnext/hr/doctype/additional_salary/additional_salary.py +++ b/erpnext/hr/doctype/additional_salary/additional_salary.py @@ -37,11 +37,12 @@ class AdditionalSalary(Document): @frappe.whitelist() def get_additional_salary_component(employee, start_date, end_date): additional_components = frappe.db.sql(""" - select salary_component, sum(amount) as amount from `tabAdditional Salary` + select salary_component, sum(amount) as amount, overwrite_salary_structure_amount from `tabAdditional Salary` where employee=%(employee)s and docstatus = 1 and payroll_date between %(from_date)s and %(to_date)s - group by salary_component + group by salary_component, overwrite_salary_structure_amount + order by salary_component, overwrite_salary_structure_amount """, { 'employee': employee, 'from_date': start_date, @@ -58,6 +59,7 @@ def get_additional_salary_component(employee, start_date, end_date): additional_components_list.append({ 'amount': d.amount, 'type': component.type, - 'struct_row': struct_row + 'struct_row': struct_row, + 'overwrite': d.overwrite_salary_structure_amount }) return additional_components_list \ No newline at end of file diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index 3d9f8e60bb..fa1b63cee8 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -20,48 +20,61 @@ frappe.ui.form.on('Payroll Entry', { }, refresh: function(frm) { + if (frm.doc.docstatus == 0) { + if(!frm.is_new()) { + frm.page.clear_primary_action(); + frm.add_custom_button(__("Get Employees"), + function() { + frm.events.get_employee_details(frm); + } + ).toggleClass('btn-primary', !(frm.doc.employees || []).length); + } + if ((frm.doc.employees || []).length) { + frm.page.set_primary_action(__('Create Salary Slips'), () => { + frm.save('Submit'); + }); + } + } if (frm.doc.docstatus == 1) { if (frm.custom_buttons) frm.clear_custom_buttons(); frm.events.add_context_buttons(frm); } }, - add_context_buttons: function(frm) { - frappe.call({ - method: 'erpnext.hr.doctype.payroll_entry.payroll_entry.payroll_entry_has_created_slips', - args: { - 'name': frm.doc.name - }, + get_employee_details: function (frm) { + return frappe.call({ + doc: frm.doc, + method: 'fill_employee_details', callback: function(r) { - if(r.message) { - frm.events.add_salary_slip_buttons(frm, r.message); - if(r.message.submitted){ - frm.events.add_bank_entry_button(frm); + if (r.docs[0].employees){ + frm.save(); + frm.refresh(); + if(r.docs[0].validate_attendance){ + render_employee_attendance(frm, r.message); } } } - }); + }) }, - add_salary_slip_buttons: function(frm, slip_status) { - if (!slip_status.draft && !slip_status.submitted) { - return; - } else { - frm.add_custom_button(__("View Salary Slips"), - function() { - frappe.set_route( - 'List', 'Salary Slip', {posting_date: frm.doc.posting_date} - ); - } - ); - } + create_salary_slips: function(frm) { + frm.call({ + doc: frm.doc, + method: "create_salary_slips", + callback: function(r) { + frm.refresh(); + frm.toolbar.refresh(); + } + }) + }, - if (slip_status.draft) { - frm.add_custom_button(__("Submit Salary Slip"), - function() { - submit_salary_slip(frm); - } - ).addClass("btn-primary"); + add_context_buttons: function(frm) { + if(frm.doc.salary_slips_submitted) { + frm.events.add_bank_entry_button(frm); + } else if(frm.doc.salary_slips_created) { + frm.add_custom_button(__("Submit Salary Slip"), function() { + submit_salary_slip(frm); + }).addClass("btn-primary"); } }, @@ -73,13 +86,9 @@ frappe.ui.form.on('Payroll Entry', { }, callback: function(r) { if (r.message && !r.message.submitted) { - frm.add_custom_button("Bank Entry", - function() { - make_bank_entry(frm); - }, - __('Make') - ); - frm.page.set_inner_btn_group_as_primary(__('Make')); + frm.add_custom_button("Make Bank Entry", function() { + make_bank_entry(frm); + }).addClass("btn-primary"); } } }); @@ -115,23 +124,23 @@ frappe.ui.form.on('Payroll Entry', { payroll_frequency: function (frm) { frm.trigger("set_start_end_dates"); - frm.set_value('employees', []); + frm.events.clear_employee_table(frm); }, company: function (frm) { - frm.set_value('employees', []); + frm.events.clear_employee_table(frm); }, department: function (frm) { - frm.set_value('employees', []); + frm.events.clear_employee_table(frm); }, designation: function (frm) { - frm.set_value('employees', []); + frm.events.clear_employee_table(frm); }, branch: function (frm) { - frm.set_value('employees', []); + frm.events.clear_employee_table(frm); }, start_date: function (frm) { @@ -141,11 +150,11 @@ frappe.ui.form.on('Payroll Entry', { // reset flag in_progress = false; } - frm.set_value('employees', []); + frm.events.clear_employee_table(frm); }, project: function (frm) { - frm.set_value('employees', []); + frm.events.clear_employee_table(frm); }, salary_slip_based_on_timesheet: function (frm) { @@ -201,7 +210,12 @@ frappe.ui.form.on('Payroll Entry', { }else{ frm.fields_dict.attendance_detail_html.html(""); } - } + }, + + clear_employee_table: function (frm) { + frm.clear_table('employees'); + frm.refresh(); + }, }); // Submit salary slips @@ -227,18 +241,6 @@ const submit_salary_slip = function (frm) { ); }; -cur_frm.cscript.get_employee_details = function (doc) { - var callback = function (r) { - if (r.docs[0].employees){ - cur_frm.refresh_field('employees'); - if(r.docs[0].validate_attendance){ - render_employee_attendance(cur_frm, r.message); - } - } - }; - return $c('runserverobj', { 'method': 'fill_employee_details', 'docs': doc }, callback); -}; - let make_bank_entry = function (frm) { var doc = frm.doc; if (doc.company && doc.start_date && doc.end_date && doc.payment_account) { @@ -247,7 +249,7 @@ let make_bank_entry = function (frm) { method: "make_payment_entry", callback: function() { frappe.set_route( - 'List', 'Journal Entry', {posting_date: frm.doc.posting_date} + 'List', 'Journal Entry', {"Journal Entry Account.reference_name": frm.doc.name} ); }, freeze: true, diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json index 2cb67518b6..7dd8d9cef1 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.json +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json @@ -77,40 +77,6 @@ "unique": 0, "width": "50%" }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -211,6 +177,73 @@ "unique": 0, "width": "50%" }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": "", + "columns": 0, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employees", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -277,6 +310,37 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -317,8 +381,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", + "fieldname": "number_of_employees", + "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -326,13 +390,14 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Number Of Employees", "length": 0, "no_copy": 0, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -348,8 +413,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "get_employee_details", - "fieldtype": "Button", + "fieldname": "sec_break20", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -357,7 +422,6 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Get Employee Details", "length": 0, "no_copy": 0, "permlevel": 0, @@ -389,7 +453,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Employees", + "label": "Employee Details", "length": 0, "no_copy": 0, "options": "Payroll Employee Detail", @@ -797,6 +861,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_from": "company.cost_center", "fieldname": "cost_center", "fieldtype": "Link", "hidden": 0, @@ -1016,38 +1081,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "activity_log", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Activity Log", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1079,6 +1112,70 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "salary_slips_created", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Salary Slips Created", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "salary_slips_submitted", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Salary Slips Submitted", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -1092,7 +1189,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-06-28 13:55:48.295327", + "modified": "2018-07-30 14:57:37.601430", "modified_by": "Administrator", "module": "HR", "name": "Payroll Entry", @@ -1126,5 +1223,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 0, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 7eaa063752..0ba996059c 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -66,6 +66,7 @@ class PayrollEntry(Document): for d in employees: self.append('employees', d) + self.number_of_employees = len(employees) if self.validate_attendance: return self.validate_employee_attendance() @@ -108,9 +109,9 @@ class PayrollEntry(Document): "posting_date": self.posting_date, "deduct_tax_for_unclaimed_employee_benefits": self.deduct_tax_for_unclaimed_employee_benefits, "deduct_tax_for_unsubmitted_tax_exemption_proof": self.deduct_tax_for_unsubmitted_tax_exemption_proof, - "payroll_entry": self.payroll_entry + "payroll_entry": self.name }) - if len(emp_list) > 50: + if len(emp_list) > 30: frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args) else: create_salary_slips_for_employees(emp_list, args, publish_progress=False) @@ -129,41 +130,12 @@ class PayrollEntry(Document): return ss_list def submit_salary_slips(self): - """ - Submit all salary slips based on selected criteria - """ self.check_permission('write') - ss_list = self.get_sal_slip_list(ss_status=0) - submitted_ss = [] - not_submitted_ss = [] - frappe.flags.via_payroll_entry = True - for ss in ss_list: - ss_obj = frappe.get_doc("Salary Slip",ss[0]) - ss_dict = {} - ss_dict["Employee Name"] = ss_obj.employee_name - ss_dict["Total Pay"] = fmt_money(ss_obj.net_pay, - currency = frappe.defaults.get_global_default("currency")) - ss_dict["Salary Slip"] = format_as_links(ss_obj.name)[0] - - if ss_obj.net_pay<0: - not_submitted_ss.append(ss_dict) - else: - try: - ss_obj.submit() - submitted_ss.append(ss_obj) - - except frappe.ValidationError: - not_submitted_ss.append(ss_dict) - - if submitted_ss: - self.make_accrual_jv_entry() - frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}") - .format(ss_obj.start_date, ss_obj.end_date)) - - self.email_salary_slip(submitted_ss) - - return create_submit_log(submitted_ss, not_submitted_ss) + if len(ss_list) > 30: + frappe.enqueue(submit_salary_slips_for_employees, timeout=600, payroll_entry=self, salary_slips=ss_list) + else: + submit_salary_slips_for_employees(self, ss_list, publish_progress=False) def email_salary_slip(self, submitted_ss): if frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee"): @@ -490,49 +462,6 @@ def get_month_details(year, month): else: frappe.throw(_("Fiscal Year {0} not found").format(year)) -def format_as_links(salary_slip): - return ['{0}'.format(salary_slip)] - - -def create_submit_log(submitted_ss, not_submitted_ss): - if not submitted_ss and not not_submitted_ss: - frappe.msgprint(_("No salary slip found to submit for the above selected criteria OR salary slip already submitted")) - - if not_submitted_ss: - frappe.msgprint(_("Could not submit some Salary Slips
\ - Possible reasons:
\ - 1. Net pay is less than 0.
\ - 2. Company Email Address specified in employee master is not valid.
")) - - -def get_salary_slip_list(name, docstatus, as_dict=0): - payroll_entry = frappe.get_doc('Payroll Entry', name) - - salary_slip_list = frappe.db.sql( - "select t1.name, t1.salary_structure from `tabSalary Slip` t1 " - "where t1.docstatus = %s " - "and t1.start_date >= %s " - "and t1.end_date <= %s", - (docstatus, payroll_entry.start_date, payroll_entry.end_date), - as_dict=as_dict - ) - - return salary_slip_list - - -@frappe.whitelist() -def payroll_entry_has_created_slips(name): - response = {} - - draft_salary_slips = get_salary_slip_list(name, docstatus=0) - submitted_salary_slips = get_salary_slip_list(name, docstatus=1) - - response['draft'] = 1 if draft_salary_slips else 0 - response['submitted'] = 1 if submitted_salary_slips else 0 - - return response - - def get_payroll_entry_bank_entries(payroll_entry_name): journal_entries = frappe.db.sql( 'select name from `tabJournal Entry Account` ' @@ -548,7 +477,6 @@ def get_payroll_entry_bank_entries(payroll_entry_name): @frappe.whitelist() def payroll_entry_has_bank_entries(name): response = {} - bank_entries = get_payroll_entry_bank_entries(name) response['submitted'] = 1 if bank_entries else 0 @@ -568,6 +496,10 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True): frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)), title = _("Creating Salary Slips...")) + payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) + payroll_entry.db_set("salary_slips_created", 1) + payroll_entry.notify_update() + def get_existing_salary_slips(employees, args): return frappe.db.sql_list(""" select distinct employee from `tabSalary Slip` @@ -575,4 +507,41 @@ def get_existing_salary_slips(employees, args): and start_date >= %s and end_date <= %s and employee in (%s) """ % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))), - [args.company, args.start_date, args.end_date] + employees) \ No newline at end of file + [args.company, args.start_date, args.end_date] + employees) + +def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True): + submitted_ss = [] + not_submitted_ss = [] + frappe.flags.via_payroll_entry = True + + count = 0 + for ss in salary_slips: + ss_obj = frappe.get_doc("Salary Slip",ss[0]) + if ss_obj.net_pay<0: + not_submitted_ss.append(ss[0]) + else: + try: + ss_obj.submit() + submitted_ss.append(ss_obj) + except frappe.ValidationError: + not_submitted_ss.append(ss[0]) + + count += 1 + if publish_progress: + frappe.publish_progress(count*100/len(salary_slips), title = _("Submitting Salary Slips...")) + + if submitted_ss: + payroll_entry.make_accrual_jv_entry() + frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}") + .format(ss_obj.start_date, ss_obj.end_date)) + + payroll_entry.email_salary_slip(submitted_ss) + + payroll_entry.db_set("salary_slips_submitted", 1) + payroll_entry.notify_update() + + if not submitted_ss and not not_submitted_ss: + frappe.msgprint(_("No salary slip found to submit for the above selected criteria OR salary slip already submitted")) + + if not_submitted_ss: + frappe.msgprint(_("Could not submit some Salary Slips")) \ No newline at end of file diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry_dashboard.py b/erpnext/hr/doctype/payroll_entry/payroll_entry_dashboard.py new file mode 100644 index 0000000000..9a03a2c9ab --- /dev/null +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry_dashboard.py @@ -0,0 +1,20 @@ +from frappe import _ + +def get_data(): + return { + 'fieldname': 'payroll_entry', + 'non_standard_fieldnames': { + 'Journal Entry': 'reference_name', + 'Payment Entry': 'reference_name', + }, + 'transactions': [ + { + 'items': ['Salary Slip', 'Journal Entry'] + } + ], + 'form_links': [ + { + 'items': ['Error Log'] + } + ] + } \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index f6e3208630..a009fb0718 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -83,10 +83,11 @@ class SalarySlip(TransactionBase): for additional_component in additional_components: additional_component = frappe._dict(additional_component) amount = additional_component.amount + overwrite = additional_component.overwrite key = "earnings" if additional_component.type == "Deduction": key = "deductions" - self.update_component_row(frappe._dict(additional_component.struct_row), amount, key) + self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite) self.get_last_payroll_period_benefit() @@ -125,7 +126,7 @@ class SalarySlip(TransactionBase): if benefit_claim_amount: self.update_component_row(struct_row, benefit_claim_amount, "earnings") - def update_component_row(self, struct_row, amount, key, benefit_tax=None, additional_tax=None): + def update_component_row(self, struct_row, amount, key, benefit_tax=None, additional_tax=None, overwrite=1): component_row = None for d in self.get(key): if d.salary_component == struct_row.salary_component: @@ -146,8 +147,13 @@ class SalarySlip(TransactionBase): 'tax_on_additional_salary': additional_tax }) else: - component_row.default_amount = amount - component_row.amount = amount + if overwrite: + component_row.default_amount = amount + component_row.amount = amount + else: + component_row.default_amount += amount + component_row.amount = component_row.default_amount + component_row.tax_on_flexible_benefit = benefit_tax component_row.tax_on_additional_salary = additional_tax @@ -447,6 +453,9 @@ class SalarySlip(TransactionBase): self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) 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")) def set_loan_repayment(self): self.set('loans', [])