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:
parent
b73b347697
commit
bd6e8b9cec
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user