Feature - Employee Attendance Tool
diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py
index 4501be7..e147112 100644
--- a/erpnext/config/hr.py
+++ b/erpnext/config/hr.py
@@ -60,9 +60,9 @@
"items": [
{
"type": "doctype",
- "name": "Process Payroll",
- "label": _("Process Payroll"),
- "description":_("Generate Salary Slips"),
+ "name": "Employee Attendance Tool",
+ "label": _("Employee Attendance Tool"),
+ "description":_("Mark Employee Attendance in Bulk"),
"hide_count": True
},
{
@@ -73,6 +73,14 @@
},
{
"type": "doctype",
+ "name": "Process Payroll",
+ "label": _("Process Payroll"),
+ "description":_("Generate Salary Slips"),
+ "hide_count": True
+ },
+
+ {
+ "type": "doctype",
"name": "Leave Control Panel",
"label": _("Leave Allocation Tool"),
"description":_("Allocate leaves for the year."),
diff --git a/erpnext/hr/doctype/employee_attendance_tool/__init__.py b/erpnext/hr/doctype/employee_attendance_tool/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_attendance_tool/__init__.py
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css
new file mode 100644
index 0000000..d25fb22
--- /dev/null
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css
@@ -0,0 +1,21 @@
+.top-toolbar{
+ padding-bottom: 30px;
+ margin-left: -17px;
+}
+
+.bottom-toolbar{
+ margin-left: -17px;
+ margin-top: 20px;
+}
+
+.btn{
+ margin-right: 5px;
+}
+
+.marked-employee-label{
+ font-weight: normal;
+}
+
+.checkbox{
+ margin-top: -3px;
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
new file mode 100644
index 0000000..9467d39
--- /dev/null
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
@@ -0,0 +1,233 @@
+frappe.ui.form.on("Employee Attendance Tool", {
+ refresh:function(frm){
+ frm.disable_save();
+ },
+
+ onload:function(frm){
+ erpnext.employee_attendance_tool.load_employees(frm);
+ },
+
+ date:function(frm){
+ erpnext.employee_attendance_tool.load_employees(frm);
+ },
+
+ department:function(frm){
+ erpnext.employee_attendance_tool.load_employees(frm);
+ },
+
+ branch:function(frm){
+ erpnext.employee_attendance_tool.load_employees(frm);
+ },
+
+ company:function(frm){
+ erpnext.employee_attendance_tool.load_employees(frm);
+ }
+
+
+});
+
+
+erpnext.employee_attendance_tool = {
+ load_employees: function(frm){
+ if(frm.doc.date){
+ frappe.call({
+ method:"erpnext.hr.doctype.employee_attendance_tool.employee_attendance_tool.get_employees",
+ args:{
+ date:frm.doc.date,
+ department:frm.doc.department,
+ branch:frm.doc.branch,
+ company:frm.doc.company
+ },
+ callback:function(r){
+ if(r.message['unmarked'].length > 0){
+ unhide_field('unmarked_attendance_section')
+ if(!frm.employee_area){
+ frm.employee_area = $('<div>')
+ .appendTo(frm.fields_dict.employees_html.wrapper);
+ }
+ frm.EmployeeSelector = new erpnext.EmployeeSelector(frm, frm.employee_area, r.message['unmarked'])
+ }
+ else{
+ hide_field('unmarked_attendance_section')
+ }
+
+ if(r.message['marked'].length > 0){
+ unhide_field('marked_attendance_section')
+ if(!frm.marked_employee_area){
+
+ frm.marked_employee_area = $('<div>')
+ .appendTo(frm.fields_dict.marked_attendance_html.wrapper);
+ }
+ frm.MarkedEmployee = new erpnext.MarkedEmployee(frm, frm.marked_employee_area, r.message['marked'])
+ }
+ else{
+ hide_field('marked_attendance_section')
+ }
+ }
+ });
+ }
+ }
+}
+
+erpnext.MarkedEmployee = Class.extend({
+ init: function(frm, wrapper, employee) {
+ this.wrapper = wrapper;
+ this.frm = frm;
+ this.make(frm, employee);
+ },
+ make: function(frm, employee) {
+ var me = this;
+ $(this.wrapper).empty();
+
+ $.each(employee, function(i, m) {
+ var attendance_icon = "'icon-check'"
+ if(m.status == "Absent") {
+ attendance_icon="'icon-check-empty'"
+ }
+ else if(m.status == "Half Day"){
+ attendance_icon = "'icon-check-minus'"
+ }
+ $(repl('<div class="col-sm-3">\
+ <label class="marked-employee-label"><span class=%(icon)s></span>\
+ %(employee)s</label>\
+ </div>', {employee: m.employee_name, icon: attendance_icon})).appendTo(me.wrapper);
+ });
+ }
+});
+
+
+erpnext.EmployeeSelector = Class.extend({
+ init: function(frm, wrapper, employee) {
+ this.wrapper = wrapper;
+ this.frm = frm;
+ this.make(frm, employee);
+ },
+ make: function(frm, employee) {
+ var me = this;
+
+ $(this.wrapper).empty();
+ var employee_toolbar = $('<div class="col-sm-12 top-toolbar">\
+ <button class="btn btn-default btn-add btn-xs"></button>\
+ <button class="btn btn-xs btn-default btn-remove"></button>\
+ </div>').appendTo($(this.wrapper));
+
+ var mark_employee_toolbar = $('<div class="col-sm-12 bottom-toolbar">\
+ <button class="btn btn-default btn-primary btn-mark-present btn-xs"></button>\
+ <button class="btn btn-default btn-primary btn-mark-absent btn-xs"></button>\
+ <button class="btn btn-default btn-primary btn-mark-half-day btn-xs"></button></div>')
+
+ employee_toolbar.find(".btn-add")
+ .html(__('Check all'))
+ .on("click", function() {
+ $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) {
+ if(!$(check).is(":checked")) {
+ check.checked = true;
+ }
+ });
+ });
+
+ employee_toolbar.find(".btn-remove")
+ .html(__('Uncheck all'))
+ .on("click", function() {
+ $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) {
+ if($(check).is(":checked")) {
+ check.checked = false;
+ }
+ });
+ });
+
+ mark_employee_toolbar.find(".btn-mark-present")
+ .html(__('Mark Present'))
+ .on("click", function() {
+ var employee_present = [];
+ $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) {
+ if($(check).is(":checked")) {
+ employee_present.push(employee[i]);
+ }
+ });
+ frappe.call({
+ method: "erpnext.hr.doctype.employee_attendance_tool.employee_attendance_tool.mark_employee_attendance",
+ args:{
+ "employee_list":employee_present,
+ "status":"Present",
+ "date":frm.doc.date,
+ "company":frm.doc.company
+ },
+
+ callback: function(r) {
+ erpnext.employee_attendance_tool.load_employees(frm);
+
+ }
+ });
+ });
+
+ mark_employee_toolbar.find(".btn-mark-absent")
+ .html(__('Mark Absent'))
+ .on("click", function() {
+ var employee_absent = [];
+ $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) {
+ if($(check).is(":checked")) {
+ employee_absent.push(employee[i]);
+ }
+ });
+ frappe.call({
+ method: "erpnext.hr.doctype.employee_attendance_tool.employee_attendance_tool.mark_employee_attendance",
+ args:{
+ "employee_list":employee_absent,
+ "status":"Absent",
+ "date":frm.doc.date,
+ "company":frm.doc.company
+ },
+
+ callback: function(r) {
+ erpnext.employee_attendance_tool.load_employees(frm);
+
+ }
+ });
+ });
+
+
+ mark_employee_toolbar.find(".btn-mark-half-day")
+ .html(__('Mark Half Day'))
+ .on("click", function() {
+ var employee_half_day = [];
+ $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) {
+ if($(check).is(":checked")) {
+ employee_half_day.push(employee[i]);
+ }
+ });
+ frappe.call({
+ method: "erpnext.hr.doctype.employee_attendance_tool.employee_attendance_tool.mark_employee_attendance",
+ args:{
+ "employee_list":employee_half_day,
+ "status":"Half Day",
+ "date":frm.doc.date,
+ "company":frm.doc.company
+ },
+
+ callback: function(r) {
+ erpnext.employee_attendance_tool.load_employees(frm);
+
+ }
+ });
+ });
+
+
+ $.each(employee, function(i, m) {
+ $(repl('<div class="col-sm-3 unmarked-employee-checkbox">\
+ <div class="checkbox">\
+ <label><input type="checkbox" class="employee-check" employee="%(employee)s"/>\
+ %(employee)s</label>\
+ </div></div>', {employee: m.employee_name})).appendTo(me.wrapper);
+
+
+
+ });
+
+ mark_employee_toolbar.appendTo($(this.wrapper));
+
+
+ }
+});
+
+
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.json b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.json
new file mode 100644
index 0000000..f31bcf8
--- /dev/null
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.json
@@ -0,0 +1,275 @@
+{
+ "allow_copy": 1,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "creation": "2016-01-27 14:59:47.849379",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "default": "Today",
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Department",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Department",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "branch",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Branch",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Branch",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 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,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "depends_on": "date",
+ "fieldname": "unmarked_attendance_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Unmarked Attendance",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "employees_html",
+ "fieldtype": "HTML",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Employees HTML",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "depends_on": "date",
+ "fieldname": "marked_attendance_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Marked Attendance",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "marked_attendance_html",
+ "fieldtype": "HTML",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Marked Attendance HTML",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "hide_heading": 1,
+ "hide_toolbar": 1,
+ "idx": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 1,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2016-01-29 02:14:36.034952",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Attendance Tool",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 0,
+ "email": 0,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 0,
+ "read": 1,
+ "report": 0,
+ "role": "HR Manager",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
new file mode 100644
index 0000000..622b0d9
--- /dev/null
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import copy
+import json
+from frappe.model.document import Document
+
+
+class EmployeeAttendanceTool(Document):
+ pass
+
+
+@frappe.whitelist()
+def get_employees(date, department=None, branch=None, company=None):
+ attendance_not_marked = []
+ attendance_marked = []
+ employee_list = frappe.get_list("Employee", fields=["employee", "employee_name"], filters={
+ "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}):
+ marked_employee[emp['employee']] = emp['status']
+
+ for employee in employee_list:
+ employee['status'] = marked_employee.get(employee['employee'])
+ if employee['employee'] not in marked_employee:
+ attendance_not_marked.append(employee)
+ else:
+ attendance_marked.append(employee)
+ return {
+ "marked": attendance_marked,
+ "unmarked": attendance_not_marked
+ }
+
+
+@frappe.whitelist()
+def mark_employee_attendance(employee_list, status, date, 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.status = status
+ if company:
+ attendance.company = company
+ else:
+ attendance.company = frappe.db.get_value("Employee", employee['employee'], "Company")
+ attendance.submit()