From a10f118ddd219200817b1844090ee42108780e15 Mon Sep 17 00:00:00 2001 From: Ranjith Kurungadam Date: Thu, 28 Jun 2018 23:28:07 +0530 Subject: [PATCH] Payroll Entry - Validate Attendance (#14738) --- .../hr/doctype/payroll_entry/payroll_entry.js | 29 ++++ .../doctype/payroll_entry/payroll_entry.json | 126 +++++++++++++++++- .../hr/doctype/payroll_entry/payroll_entry.py | 45 ++++++- erpnext/public/build.json | 1 + .../employees_to_mark_attendance.html | 14 ++ 5 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 erpnext/public/js/templates/employees_to_mark_attendance.html diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index 26008d99f7..2f6f4b2a97 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -185,6 +185,23 @@ frappe.ui.form.on('Payroll Entry', { } }); }, + + validate_attendance: function(frm){ + if(frm.doc.validate_attendance && frm.doc.employees){ + frappe.call({ + method: 'validate_employee_attendance', + args: {}, + callback: function(r) { + render_employee_attendance(frm, r.message); + }, + doc: frm.doc, + freeze: true, + freeze_message: 'Validating Employee Attendance...' + }); + }else{ + frm.fields_dict.attendance_detail_html.html(""); + } + } }); // Submit salary slips @@ -214,6 +231,9 @@ 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); @@ -237,3 +257,12 @@ let make_bank_entry = function (frm) { frappe.msgprint(__("Company, Payment Account, From Date and To Date is mandatory")); } }; + + +let render_employee_attendance = function(frm, data) { + frm.fields_dict.attendance_detail_html.html( + frappe.render_template('employees_to_mark_attendance', { + data: data + }) + ); +} diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json index 54f1e45d0d..2cb67518b6 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.json +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json @@ -15,6 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -46,6 +47,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -77,6 +79,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -110,6 +113,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -142,6 +146,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -176,6 +181,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -207,6 +213,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -239,6 +246,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -271,6 +279,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -303,6 +312,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -333,6 +343,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -364,6 +375,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -396,6 +408,101 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_13", + "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, + "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "validate_attendance", + "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": "Validate Attendance", + "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "attendance_detail_html", + "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, + "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, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -426,6 +533,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -459,6 +567,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -490,6 +599,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -522,6 +632,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -554,6 +665,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -584,6 +696,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -615,6 +728,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -646,6 +760,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -677,6 +792,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -709,6 +825,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -739,6 +856,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -771,6 +889,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -802,6 +921,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -833,6 +953,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 1, "bold": 0, "collapsible": 0, @@ -866,6 +987,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -896,6 +1018,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -927,6 +1050,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -968,7 +1092,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-27 12:42:45.054509", + "modified": "2018-06-28 13:55:48.295327", "modified_by": "Administrator", "module": "HR", "name": "Payroll Entry", diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index ed4957db68..4f161ecc7e 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -6,16 +6,21 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from dateutil.relativedelta import relativedelta -from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT +from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff from frappe import _ from erpnext.accounts.utils import get_fiscal_year - +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class PayrollEntry(Document): def on_submit(self): self.create_salary_slips() + def before_submit(self): + if self.validate_attendance: + if self.validate_employee_attendance(): + frappe.throw(_("Cannot Submit, Employees left to mark attendance")) + def get_emp_list(self): """ Returns list of active employees based on selected criteria @@ -61,6 +66,9 @@ class PayrollEntry(Document): for d in employees: self.append('employees', d) + if self.validate_attendance: + return self.validate_employee_attendance() + def get_filter_condition(self): self.check_mandatory() @@ -381,6 +389,39 @@ class PayrollEntry(Document): self.update(get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date, self.company)) + def validate_employee_attendance(self): + employees_to_mark_attendance = [] + days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 + for employee_detail in self.employees: + days_holiday = self.get_count_holidays_of_employee(employee_detail.employee) + days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee) + days_in_payroll = date_diff(self.end_date, self.start_date) + 1 + if days_in_payroll > days_holiday + days_attendance_marked: + employees_to_mark_attendance.append({ + "employee": employee_detail.employee, + "employee_name": employee_detail.employee_name + }) + return employees_to_mark_attendance + + def get_count_holidays_of_employee(self, employee): + holiday_list = get_holiday_list_for_employee(employee) + holidays = 0 + if holiday_list: + days = frappe.db.sql("""select count(*) from tabHoliday where + parent=%s and holiday_date between %s and %s""", (holiday_list, + self.start_date, self.end_date)) + if days and days[0][0]: + holidays = days[0][0] + return holidays + + def get_count_employee_attendance(self, employee): + marked_days = 0 + attendances = frappe.db.sql("""select count(*) from tabAttendance where + employee=%s and docstatus=1 and attendance_date between %s and %s""", + (employee, self.start_date, self.end_date)) + if attendances and attendances[0][0]: + marked_days = attendances[0][0] + return marked_days @frappe.whitelist() def get_start_end_dates(payroll_frequency, start_date=None, company=None): diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 24ccfbf6f0..ed4ebababe 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -30,6 +30,7 @@ "public/js/payment/pos_payment.html", "public/js/payment/payment_details.html", "public/js/templates/item_selector.html", + "public/js/templates/employees_to_mark_attendance.html", "public/js/utils/item_selector.js", "public/js/help_links.js", "public/js/agriculture/ternary_plot.js", diff --git a/erpnext/public/js/templates/employees_to_mark_attendance.html b/erpnext/public/js/templates/employees_to_mark_attendance.html new file mode 100644 index 0000000000..167c775636 --- /dev/null +++ b/erpnext/public/js/templates/employees_to_mark_attendance.html @@ -0,0 +1,14 @@ +{% if data %} +
+
+ Employees to mark attendance +
+ {% for item in data %} +
+ {{ item.employee }}    {{ item.employee_name }} +
+ {% } %} +
+{% } else { %} +
+{% } %}