feat: Mark Unmarked Attendance (#20062)

* feat: Mark Unmarked Attendance

* Update shift_type.py

* Update attendance_list.js

* Update attendance.py

* Update attendance.py

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
Anurag Mishra 2019-12-31 17:09:56 +05:30 committed by Nabin Hait
parent b73b347697
commit bd6e8b9cec
4 changed files with 182 additions and 13 deletions

View File

@ -7,7 +7,8 @@ import frappe
from frappe.utils import getdate, nowdate
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr
from frappe.utils import cstr, get_datetime, get_datetime_str
from frappe.utils import update_progress_bar
class Attendance(Document):
def validate_duplicate_record(self):
@ -89,17 +90,85 @@ def add_attendance(events, start, end, conditions=None):
if e not in events:
events.append(e)
def mark_absent(employee, attendance_date, shift=None):
def mark_attendance(employee, attendance_date, status, shift=None):
employee_doc = frappe.get_doc('Employee', employee)
if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
doc_dict = {
'doctype': 'Attendance',
'employee': employee,
'attendance_date': attendance_date,
'status': 'Absent',
'status': status,
'company': employee_doc.company,
'shift': shift
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
return attendance.name
@frappe.whitelist()
def mark_bulk_attendance(data):
import json
from pprint import pprint
if isinstance(data, frappe.string_types):
data = json.loads(data)
data = frappe._dict(data)
company = frappe.get_value('Employee', data.employee, 'company')
for date in data.unmarked_days:
doc_dict = {
'doctype': 'Attendance',
'employee': data.employee,
'attendance_date': get_datetime(date),
'status': data.status,
'company': company,
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
def get_month_map():
return frappe._dict({
"January": 1,
"February": 2,
"March": 3,
"April": 4,
"May": 5,
"June": 6,
"July": 7,
"August": 8,
"September": 9,
"October": 10,
"November": 11,
"December": 12
})
@frappe.whitelist()
def get_unmarked_days(employee, month):
import calendar
month_map = get_month_map()
today = get_datetime()
dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(1, calendar.monthrange(today.year, month_map[month])[1] + 1)]
length = len(dates_of_month)
month_start, month_end = dates_of_month[0], dates_of_month[length-1]
records = frappe.get_all("Attendance", fields = ['attendance_date', 'employee'] , filters = [
["attendance_date", ">", month_start],
["attendance_date", "<", month_end],
["employee", "=", employee],
["docstatus", "!=", 2]
])
marked_days = [get_datetime(record.attendance_date) for record in records]
unmarked_days = []
for date in dates_of_month:
date_time = get_datetime(date)
if today.day == date_time.day and today.month == date_time.month:
break
if date_time not in marked_days:
unmarked_days.append(date)
return unmarked_days

View File

@ -2,5 +2,105 @@ frappe.listview_settings['Attendance'] = {
add_fields: ["status", "attendance_date"],
get_indicator: function(doc) {
return [__(doc.status), doc.status=="Present" ? "green" : "darkgrey", "status,=," + doc.status];
},
onload: function(list_view) {
let me = this;
const months = moment.months()
list_view.page.add_inner_button( __("Mark Attendance"), function(){
let dialog = new frappe.ui.Dialog({
title: __("Mark Attendance"),
fields: [
{
fieldname: 'employee',
label: __('For Employee'),
fieldtype: 'Link',
options: 'Employee',
reqd: 1,
onchange: function(){
dialog.set_df_property("unmarked_days", "hidden", 1);
dialog.set_df_property("status", "hidden", 1);
dialog.set_df_property("month", "value", '');
dialog.set_df_property("unmarked_days", "options", []);
}
},
{
label: __("For Month"),
fieldtype: "Select",
fieldname: "month",
options: months,
reqd: 1,
onchange: function(){
if(dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
dialog.set_df_property("status", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", []);
me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options =>{
dialog.set_df_property("unmarked_days", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", options);
});
}
}
},
{
label: __("Status"),
fieldtype: "Select",
fieldname: "status",
options: ["Present", "Absent", "Half Day"],
hidden:1,
reqd: 1,
},
{
label: __("Unmarked Attendance for days"),
fieldname: "unmarked_days",
fieldtype: "MultiCheck",
options: [],
columns: 2,
hidden: 1
},
],
primary_action(data){
frappe.confirm(__('Mark attendance as <b>' + data.status + '</b> for <b>' + data.month +'</b>' + ' on selected dates?'), () => {
frappe.call({
method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance",
args: {
data : data
},
callback: function(r) {
if(r.message === 1) {
frappe.show_alert({message:__("Attendance Marked"), indicator:'blue'});
cur_dialog.hide();
}
}
});
});
dialog.hide();
list_view.refresh();
},
primary_action_label: __('Mark Attendance')
});
dialog.show();
});
},
get_multi_select_options: function(employee, month){
return new Promise(resolve => {
frappe.call({
method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days',
async: false,
args:{
employee: employee,
month: month,
}
}).then(r => {
var options = [];
for(var d in r.message){
var momentObj = moment(r.message[d], 'YYYY-MM-DD');
var date = momentObj.format('DD-MM-YYYY');
options.push({ "label":date, "value": r.message[d] , "checked": 1});
}
resolve(options);
});
});
}
};

View File

@ -14,7 +14,7 @@ class TestAttendance(unittest.TestCase):
employee = make_employee("test_mark_absent@example.com")
date = nowdate()
frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date})
from erpnext.hr.doctype.attendance.attendance import mark_absent
attendance = mark_absent(employee, date)
from erpnext.hr.doctype.attendance.attendance import mark_attendance
attendance = mark_attendance(employee, date, 'Absent')
fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'})
self.assertEqual(attendance, fetch_attendance)

View File

@ -11,7 +11,7 @@ from frappe.model.document import Document
from frappe.utils import cint, getdate, get_datetime
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift, get_employee_shift
from erpnext.hr.doctype.employee_checkin.employee_checkin import mark_attendance_and_link_log, calculate_working_hours
from erpnext.hr.doctype.attendance.attendance import mark_absent
from erpnext.hr.doctype.attendance.attendance import mark_attendance
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class ShiftType(Document):
@ -35,7 +35,7 @@ class ShiftType(Document):
def get_attendance(self, logs):
"""Return attendance_status, working_hours for a set of logs belonging to a single shift.
Assumtion:
Assumtion:
1. These logs belongs to an single shift, single employee and is not in a holiday date.
2. Logs are in chronological order
"""
@ -43,10 +43,10 @@ class ShiftType(Document):
total_working_hours, in_time, out_time = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on)
if cint(self.enable_entry_grace_period) and in_time and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period)):
late_entry = True
if cint(self.enable_exit_grace_period) and out_time and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period)):
early_exit = True
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
return 'Absent', total_working_hours, late_entry, early_exit
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
@ -75,7 +75,7 @@ class ShiftType(Document):
for date in dates:
shift_details = get_employee_shift(employee, date, True)
if shift_details and shift_details.shift_type.name == self.name:
mark_absent(employee, date, self.name)
mark_attendance(employee, date, self.name, 'Absent')
def get_assigned_employee(self, from_date=None, consider_default_shift=False):
filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'}
@ -107,15 +107,15 @@ def get_filtered_date_list(employee, start_date, end_date, filter_attendance=Tru
condition_query = ''
if filter_attendance:
condition_query += """ and a.selected_date not in (
select attendance_date from `tabAttendance`
where docstatus = '1' and employee = %(employee)s
select attendance_date from `tabAttendance`
where docstatus = 1 and employee = %(employee)s
and attendance_date between %(start_date)s and %(end_date)s)"""
if holiday_list:
condition_query += """ and a.selected_date not in (
select holiday_date from `tabHoliday` where parenttype = 'Holiday List' and
parentfield = 'holidays' and parent = %(holiday_list)s
and holiday_date between %(start_date)s and %(end_date)s)"""
dates = frappe.db.sql("""select * from
({base_dates_query}) as a
where a.selected_date <= %(end_date)s {condition_query}