Payroll Entry - Validate Attendance (#14738)
diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js
index 26008d9..2f6f4b2 100644
--- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js
@@ -185,6 +185,23 @@
}
});
},
+
+ 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 @@
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 @@
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 54f1e45..2cb6751 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 ed4957d..4f161ec 100644
--- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py
@@ -6,16 +6,21 @@
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 @@
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 @@
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 24ccfbf..ed4ebab 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 0000000..167c775
--- /dev/null
+++ b/erpnext/public/js/templates/employees_to_mark_attendance.html
@@ -0,0 +1,14 @@
+{% if data %}
+<div class="col-md-12 col-xs-12" align="center">
+ <div class="col-md-12 col-xs-12" style="padding-bottom:15px;">
+ <b>Employees to mark attendance</b>
+ </div>
+ {% for item in data %}
+ <div class="col-md-4 col-xs-6" style="padding-bottom:10px;">
+ {{ item.employee }} {{ item.employee_name }}
+ </div>
+ {% } %}
+</div>
+{% } else { %}
+<div></div>
+{% } %}