From 7933eb685c960677db1d560b2a1276810936ca8a Mon Sep 17 00:00:00 2001 From: Kanchan Chauhan Date: Thu, 12 Jan 2017 17:24:53 +0530 Subject: [PATCH] [Enhancement] Attendance Module --- erpnext/demo/user/hr.py | 8 ++-- erpnext/hr/doctype/attendance/attendance.js | 2 +- erpnext/hr/doctype/attendance/attendance.json | 20 +++++----- erpnext/hr/doctype/attendance/attendance.py | 39 +++++++++---------- .../hr/doctype/attendance/attendance_list.js | 2 +- .../hr/doctype/attendance/test_records.json | 2 +- erpnext/hr/doctype/employee/employee.py | 6 +-- .../employee_attendance_tool.py | 8 ++-- .../leave_application/leave_application.py | 3 +- .../upload_attendance/upload_attendance.py | 13 ++++--- .../employees_working_on_a_holiday.py | 4 +- .../monthly_attendance_sheet.py | 18 +++++---- 12 files changed, 66 insertions(+), 59 deletions(-) diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py index cfe31197bc..fb38db93d0 100644 --- a/erpnext/demo/user/hr.py +++ b/erpnext/demo/user/hr.py @@ -176,18 +176,18 @@ def make_leave_application(): frappe.db.rollback() def mark_attendance(): - att_date = frappe.flags.current_date + attendance_date = frappe.flags.current_date for employee in frappe.get_all('Employee', fields=['name'], filters = {'status': 'Active'}): - if not frappe.db.get_value("Attendance", {"employee": employee.name, "att_date": att_date}): + if not frappe.db.get_value("Attendance", {"employee": employee.name, "attendance_date": attendance_date}): attendance = frappe.get_doc({ "doctype": "Attendance", "employee": employee.name, - "att_date": att_date + "attendance_date": attendance_date }) leave = frappe.db.sql("""select name from `tabLeave Application` where employee = %s and %s between from_date and to_date and status = 'Approved' - and docstatus = 1""", (employee.name, att_date)) + and docstatus = 1""", (employee.name, attendance_date)) if leave: attendance.status = "Absent" diff --git a/erpnext/hr/doctype/attendance/attendance.js b/erpnext/hr/doctype/attendance/attendance.js index 2aeeb9f544..7f2b18f0e4 100644 --- a/erpnext/hr/doctype/attendance/attendance.js +++ b/erpnext/hr/doctype/attendance/attendance.js @@ -5,7 +5,7 @@ cur_frm.add_fetch('employee', 'company', 'company'); cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); cur_frm.cscript.onload = function(doc, cdt, cdn) { - if(doc.__islocal) cur_frm.set_value("att_date", get_today()); + if(doc.__islocal) cur_frm.set_value("attendance_date", get_today()); } cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) { diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json index 9a1b66e799..9bbb3e5830 100644 --- a/erpnext/hr/doctype/attendance/attendance.json +++ b/erpnext/hr/doctype/attendance/attendance.json @@ -106,7 +106,7 @@ "collapsible": 0, "columns": 0, "fieldname": "employee_name", - "fieldtype": "Data", + "fieldtype": "Read Only", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -148,7 +148,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nPresent\nAbsent\nHalf Day", + "options": "\nPresent\nAbsent\nOn Leave\nHalf Day", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -165,9 +165,10 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:doc.status==\"On Leave\"", "fieldname": "leave_type", "fieldtype": "Link", - "hidden": 1, + "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -180,11 +181,11 @@ "oldfieldtype": "Link", "options": "Leave Type", "permlevel": 0, - "print_hide": 1, + "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, - "report_hide": 1, + "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 0, @@ -223,7 +224,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "att_date", + "fieldname": "attendance_date", "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, @@ -234,7 +235,7 @@ "label": "Attendance Date", "length": 0, "no_copy": 0, - "oldfieldname": "att_date", + "oldfieldname": "attendance_date", "oldfieldtype": "Date", "permlevel": 0, "print_hide": 0, @@ -317,7 +318,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-07 05:50:01.058617", + "modified": "2017-01-12 16:09:27.301839", "modified_by": "Administrator", "module": "HR", "name": "Attendance", @@ -390,9 +391,10 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, - "search_fields": "employee, employee_name, att_date, status", + "search_fields": "employee, employee_name, attendance_date, status", "sort_field": "modified", "sort_order": "DESC", "title_field": "employee_name", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index feecae08eb..465c772d65 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -11,26 +11,31 @@ from erpnext.hr.utils import set_employee_name class Attendance(Document): def validate_duplicate_record(self): - res = frappe.db.sql("""select name from `tabAttendance` where employee = %s and att_date = %s + res = frappe.db.sql("""select name from `tabAttendance` where employee = %s and attendance_date = %s and name != %s and docstatus = 1""", - (self.employee, self.att_date, self.name)) + (self.employee, self.attendance_date, self.name)) if res: frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee)) set_employee_name(self) def check_leave_record(self): - if self.status == 'Present': - leave = frappe.db.sql("""select name from `tabLeave Application` - where employee = %s and %s between from_date and to_date and status = 'Approved' - and docstatus = 1""", (self.employee, self.att_date)) + leave_record = frappe.db.sql("""select leave_type, half_day from `tabLeave Application` + where employee = %s and %s between from_date and to_date and status = 'Approved' + and docstatus = 1""", (self.employee, self.attendance_date), as_dict=True) + if leave_record: + if leave_record[0].half_day: + self.status = 'Half Day' + frappe.msgprint(_("Employee {0} on Half day on {1}").format(self.employee, self.attendance_date)) + else: + self.status = 'On Leave' + self.leave_type = leave_record[0].leave_type + frappe.msgprint(_("Employee {0} on Leave on {1}").format(self.employee, self.attendance_date)) + if self.status == "On Leave" and not leave_record: + frappe.throw(_("No leave record found for employee {0} for {1}").format(self.employee, self.attendance_date)) - if leave: - frappe.throw(_("Employee {0} was on leave on {1}. Cannot mark attendance.").format(self.employee, - self.att_date)) - - def validate_att_date(self): - if getdate(self.att_date) > getdate(nowdate()): + def validate_attendance_date(self): + if getdate(self.attendance_date) > getdate(nowdate()): frappe.throw(_("Attendance can not be marked for future dates")) def validate_employee(self): @@ -41,13 +46,7 @@ class Attendance(Document): def validate(self): from erpnext.controllers.status_updater import validate_status - validate_status(self.status, ["Present", "Absent", "Half Day"]) - self.validate_att_date() + validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day"]) + self.validate_attendance_date() self.validate_duplicate_record() self.check_leave_record() - - def on_update(self): - # this is done because sometimes user entered wrong employee name - # while uploading employee attendance - employee_name = frappe.db.get_value("Employee", self.employee, "employee_name") - frappe.db.set(self, 'employee_name', employee_name) diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 05353e2be8..f36fb15a01 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['Attendance'] = { - add_fields: ["status", "att_date"], + add_fields: ["status", "attendance_date"], get_indicator: function(doc) { return [__(doc.status), doc.status=="Present" ? "green" : "darkgrey", "status,=," + doc.status]; } diff --git a/erpnext/hr/doctype/attendance/test_records.json b/erpnext/hr/doctype/attendance/test_records.json index 12983dcb49..1c8f3b5633 100644 --- a/erpnext/hr/doctype/attendance/test_records.json +++ b/erpnext/hr/doctype/attendance/test_records.json @@ -4,7 +4,7 @@ "name": "_Test Attendance 1", "employee": "_T-Employee-0001", "status": "Present", - "att_date": "2014-02-01", + "attendance_date": "2014-02-01", "company": "_Test Company" } ] diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 881853d6c4..6b80a379b5 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -161,11 +161,11 @@ class Employee(Document): def get_timeline_data(doctype, name): '''Return timeline for attendance''' - return dict(frappe.db.sql('''select unix_timestamp(att_date), count(*) + return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*) from `tabAttendance` where employee=%s - and att_date > date_sub(curdate(), interval 1 year) + and attendance_date > date_sub(curdate(), interval 1 year) and status in ('Present', 'Half Day') - group by att_date''', name)) + group by attendance_date''', name)) @frappe.whitelist() def get_retirement_date(date_of_birth=None): diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py index 353008e612..7438737028 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py @@ -20,7 +20,7 @@ def get_employees(date, department=None, branch=None, company=None): "status": "Active", "department": department, "branch": branch, "company": company}, order_by="employee_name") marked_employee = {} for emp in frappe.get_list("Attendance", fields=["employee", "status"], - filters={"att_date": date}): + filters={"attendance_date": date}): marked_employee[emp['employee']] = emp['status'] for employee in employee_list: @@ -36,14 +36,16 @@ def get_employees(date, department=None, branch=None, company=None): @frappe.whitelist() -def mark_employee_attendance(employee_list, status, date, company=None): +def mark_employee_attendance(employee_list, status, date, leave_type=None, company=None): employee_list = json.loads(employee_list) for employee in employee_list: attendance = frappe.new_doc("Attendance") attendance.employee = employee['employee'] attendance.employee_name = employee['employee_name'] - attendance.att_date = date + attendance.attendance_date = date attendance.status = status + if status == "On Leave" and leave_type: + attendance.leave_type = leave_type if company: attendance.company = company else: diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index fbf86e178b..5b565fa1de 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -216,7 +216,7 @@ class LeaveApplication(Document): LeaveApproverIdentityError) def validate_attendance(self): - attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s and (att_date between %s and %s) + attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s and (attendance_date between %s and %s) and status = "Present" and docstatus = 1""", (self.employee, self.from_date, self.to_date)) if attendance: @@ -270,6 +270,7 @@ class LeaveApplication(Document): post(**{"txt": args.message, "contact": args.message_to, "subject": args.subject, "notify": cint(self.follow_via_email)}) + @frappe.whitelist() def get_approvers(doctype, txt, searchfield, start, page_len, filters): if not filters.get("employee"): diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py index 49eb8cf78e..fc1a8ae333 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py @@ -36,7 +36,7 @@ def add_header(w): w.writerow(["Please do not change the template headings"]) w.writerow(["Status should be one of these values: " + status]) w.writerow(["If you are overwriting existing attendance records, 'ID' column mandatory"]) - w.writerow(["ID", "Employee", "Employee Name", "Date", "Status", + w.writerow(["ID", "Employee", "Employee Name", "Date", "Status", "Leave Type", "Company", "Naming Series"]) return w @@ -53,7 +53,8 @@ def add_data(w, args): row = [ existing_attendance and existing_attendance.name or "", employee.name, employee.employee_name, date, - existing_attendance and existing_attendance.status or "", employee.company, + existing_attendance and existing_attendance.status or "", + existing_attendance and existing_attendance.leave_type or "", employee.company, existing_attendance and existing_attendance.naming_series or get_naming_series(), ] w.writerow(row) @@ -71,13 +72,13 @@ def get_active_employees(): return employees def get_existing_attendance_records(args): - attendance = frappe.db.sql("""select name, att_date, employee, status, naming_series - from `tabAttendance` where att_date between %s and %s and docstatus < 2""", + attendance = frappe.db.sql("""select name, attendance_date, employee, status, leave_type, naming_series + from `tabAttendance` where attendance_date between %s and %s and docstatus < 2""", (args["from_date"], args["to_date"]), as_dict=1) existing_attendance = {} for att in attendance: - existing_attendance[tuple([att.att_date, att.employee])] = att + existing_attendance[tuple([att.attendance_date, att.employee])] = att return existing_attendance @@ -103,7 +104,7 @@ def upload(): return {"messages": msg, "error": msg} columns = [scrub(f) for f in rows[4]] columns[0] = "name" - columns[3] = "att_date" + columns[3] = "attendance_date" ret = [] error = False diff --git a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py index a0b78a60ce..60bb02cdb3 100644 --- a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py +++ b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py @@ -41,13 +41,13 @@ def get_employees(filters): holiday_names[holiday.holiday_date] = holiday.description if(holidays_list): - cond = " att_date in %(holidays_list)s" + cond = " attendance_date in %(holidays_list)s" if filters.holiday_list: cond += """ and (employee in (select employee from tabEmployee where holiday_list = %(holidays)s))""" employee_list = frappe.db.sql("""select - employee, employee_name, att_date, status + employee, employee_name, attendance_date, status from tabAttendance where %s"""% cond.format(', '.join(["%s"] * len(holidays_list))), {'holidays_list':holidays_list, diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index 525f9abdf8..05d3df565a 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -24,21 +24,23 @@ def execute(filters=None): row = [emp, emp_det.employee_name, emp_det.branch, emp_det.department, emp_det.designation, emp_det.company] - total_p = total_a = 0.0 + total_p = total_a = total_l = 0.0 for day in range(filters["total_days_in_month"]): status = att_map.get(emp).get(day + 1, "None") - status_map = {"Present": "P", "Absent": "A", "Half Day": "H", "None": ""} + status_map = {"Present": "P", "Absent": "A", "Half Day": "H", "On Leave": "L", "None": ""} row.append(status_map[status]) if status == "Present": total_p += 1 elif status == "Absent": total_a += 1 + elif status == "On Leave": + total_l += 1 elif status == "Half Day": total_p += 0.5 total_a += 0.5 - row += [total_p, total_a] + row += [total_p, total_l, total_a] data.append(row) return columns, data @@ -53,12 +55,12 @@ def get_columns(filters): for day in range(filters["total_days_in_month"]): columns.append(cstr(day+1) +"::20") - columns += [_("Total Present") + ":Float:80", _("Total Absent") + ":Float:80"] + columns += [_("Total Present") + ":Float:80", _("Total Leaves") + ":Float:80", _("Total Absent") + ":Float:80"] return columns def get_attendance_list(conditions, filters): - attendance_list = frappe.db.sql("""select employee, day(att_date) as day_of_month, - status from tabAttendance where docstatus = 1 %s order by employee, att_date""" % + attendance_list = frappe.db.sql("""select employee, day(attendance_date) as day_of_month, + status from tabAttendance where docstatus = 1 %s order by employee, attendance_date""" % conditions, filters, as_dict=1) att_map = {} @@ -77,7 +79,7 @@ def get_conditions(filters): filters["total_days_in_month"] = monthrange(cint(filters.year), filters.month)[1] - conditions = " and month(att_date) = %(month)s and year(att_date) = %(year)s" + conditions = " and month(attendance_date) = %(month)s and year(attendance_date) = %(year)s" if filters.get("company"): conditions += " and company = %(company)s" if filters.get("employee"): conditions += " and employee = %(employee)s" @@ -95,7 +97,7 @@ def get_employee_details(): @frappe.whitelist() def get_attendance_years(): - year_list = frappe.db.sql_list("""select distinct YEAR(att_date) from tabAttendance ORDER BY YEAR(att_date) DESC""") + year_list = frappe.db.sql_list("""select distinct YEAR(attendance_date) from tabAttendance ORDER BY YEAR(attendance_date) DESC""") if not year_list: year_list = [getdate().year]