From cf1580491056c86e922182bc928d59e89cb63d23 Mon Sep 17 00:00:00 2001 From: mymi14s <mymi14s@hotmail.com> Date: Thu, 28 Dec 2023 18:44:03 +0000 Subject: [PATCH] added: hourly marking --- one_fm/hooks.py | 3 +- one_fm/overrides/attendance.py | 195 ++++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 3 deletions(-) diff --git a/one_fm/hooks.py b/one_fm/hooks.py index 5a80fa80c4..65db180095 100644 --- a/one_fm/hooks.py +++ b/one_fm/hooks.py @@ -522,7 +522,8 @@ "hourly": [ # "one_fm.api.tasks.send_checkin_hourly_reminder", 'one_fm.utils.send_gp_letter_attachment_reminder3', - 'one_fm.utils.send_gp_letter_reminder' + 'one_fm.utils.send_gp_letter_reminder', + "one_fm.overrides.attendance.run_attendance_marking_hourly", ], "weekly": [ diff --git a/one_fm/overrides/attendance.py b/one_fm/overrides/attendance.py index 2e1e27b74a..32639381c0 100644 --- a/one_fm/overrides/attendance.py +++ b/one_fm/overrides/attendance.py @@ -2,9 +2,10 @@ import frappe, erpnext from frappe import _ from frappe.utils import ( - now_datetime,nowtime, cstr, getdate, get_datetime, cint, add_to_date, - datetime, today, add_days, now + now_datetime,nowtime, cstr, getdate, get_datetime, cint, add_to_date, + datetime, today, add_days, now ) +from datetime import timedelta from hrms.hr.doctype.attendance.attendance import * from hrms.hr.utils import validate_active_employee, get_holidays_for_employee from one_fm.utils import get_holiday_today @@ -997,3 +998,193 @@ def mark_timesheet_daily_attendance(timesheet_employees,start_date): frappe.db.commit() except: frappe.log_error(message=frappe.get_traceback(), title ='Timesheet Attendance') + + +class AttendanceMarking(): + """ + This class will be used to mark attendance + """ + + def __ini__(self): + self.start = None + self.end = None + + def get_datetime(self): + dt = now_datetime() + dt = dt.replace(minute=0, second=0, microsecond=0) + self.start = dt + timedelta(hours=-2) + self.end = dt + timedelta(hours=-1) + + + def get_shifts(self): + self.get_datetime() + shifts = frappe.db.sql(f""" + SELECT * FROM `tabShift Assignment` + WHERE + end_datetime BETWEEN '{self.start}' AND '{self.end}' + """, as_dict=1) + if shifts: + checkins = self.get_checkins(tuple([i.name for i in shifts]) if len(shifts)>1 else (shifts[0])) + if checkins: + # employees = [i.employee for i in shifts] + checked_in_employees = [i.employee for i in checkins] + no_checkins = [i for i in shifts if not i.employee in checked_in_employees] + if no_checkins: #create absent + for i in no_checkins: + try: + record = frappe._dict({**dict(i), **{ + "status":"Absent", "comment":"No checkin record found", "working_hours":0, + "dt":"Shift Assignment"}}) + self.create_attendance(record) + except: + pass + if checkins: # check for work hours + # start checkin + for i in checkins: + if not frappe.db.exists("Attendance", { + 'employee':i.employee, + 'attendance_date':i.shift_actual_start.date(), + 'roster_type':i.roster_type, + 'status': ["IN", ["Present", "On Leave", "Holiday", "Day Off"]] + }): + total_hours = (i.shift_actual_end - i.shift_actual_start).total_seconds() / (60*60) + half_hour = total_hours/2 + working_hours = 0 + status = "Absent" + comment = "" + if ((i.earliest_time - i.shift_actual_end).total_seconds() / (60*60)) > half_hour: + status = 'Absent' + comment = f" Checked in late, checkin in at {i.earliest_time}" + elif i.earliest_time and i.latest_time: + working_hours = ((i.latest_time - i.earliest_time).total_seconds() / (60*60)) + if working_hours < half_hour: + status = "Absent" + comment = f"Worked less than 50% of {total_hours}hrs." + else: + status = "Present" + comment = "" + elif i.earliest_time and not i.latest_time: + working_hours = (i.shift_actual_end - i.earliest_time).total_seconds() / (60*60) + if working_hours < half_hour: + status = "Absent" + comment = f"Worked less than 50% of {total_hours}hrs." + else: + status = "Present" + comment = "" + + # continue to mark attendace + try: + self.create_attendance(frappe._dict({**dict(i), **{ + "status":status, "comment":comment, "working_hours":working_hours, + "dt":"Employee Checkin"}})) + except Exception as e: + print(e) + + def get_checkins(self, shift_assignments): + query = f""" + SELECT + ec.name, + ec.owner, + ec.creation, + ec.modified, + ec.modified_by, + ec.docstatus, + ec.idx, + ec.employee, + ec.employee_name, + ec.log_type, + ec.late_entry, + ec.early_exit, + ec.time, + ec.date, + ec.skip_auto_attendance, + ec.shift_actual_start, + ec.shift_actual_end, + ec.shift_assignment, + ec.operations_shift, + ec.shift_type, + ec.roster_type, + ec.operations_site, + ec.project, + ec.company, + ec.operations_role, + ec.post_abbrv, + ec.shift_permission, + ec.actual_time, + MIN(CASE WHEN ec.log_type = 'IN' THEN ec.time END) AS earliest_time, + MAX(CASE WHEN ec.log_type = 'OUT' THEN ec.time END) AS latest_time + FROM + `tabEmployee Checkin` ec + WHERE + ec.shift_assignment in {shift_assignments} + GROUP BY + ec.shift_assignment; + """ + return frappe.db.sql(query, as_dict=1) + + + + def create_attendance(self, record, day_off=False): + # clear absent + _date = None + if record.shift_actual_start: + _date = record.shift_actual_start.date() + elif record.date: + _date = record.date + else: + _date = record.start_date + try: + frappe.db.sql(f""" + DELETE FROM `tabAttendance` WHERE employee="{record.employee}" AND + attendance_date="{_date}" + AND roster_type="{record.roster_type}" + AND status="Absent" + """) + except: + pass + frappe.db.commit() + doc = frappe._dict({}) + doc.doctype = "Attendance" + doc.employee = record.employee + doc.status = record.status + doc.attendance_date = _date + if record.shift_assignment: + doc.shift_assignment = record.shift_assignment + doc.shift = record.shift_type + doc.operations_shift = record.operations_shift + doc.site = record.site + if record.dt=="Shift Assignment": + doc.shift_assignment = record.name + doc.shift = record.shift_type + doc.operations_shift = record.shift + doc.site = record.site + if record.late_entry: + doc.late_entry= record.late_entry + if record.early_exit: + doc.early_exit = record.early_exit + # check if worked less + if record.working_hours: + doc.working_hours = record.working_hours + if record.roster_type: + doc.roster_type = record.roster_type + if record.comment: + doc.comment = record.comment + doc = frappe.get_doc(doc) + doc.status = record.status + doc.flags.ignore_validate = True + doc.save() + doc.db_set('docstatus', 1) + # updated checkins if exists + if record.dt=="Employee Checkin": + if record.earliest_time: + frappe.db.set_value("Employee Checkin", record.name, 'attendance', doc.name) + # if record.latest_checkout: + # frappe.db.set_value("Employee Checkin", record.checkout.name, 'attendance', doc.name) + frappe.db.commit() + + + +def run_attendance_marking_hourly(): + """Marks Attendances for Hourly Employees based on Employee Checkin.""" + attendance_marking = AttendanceMarking() + frappe.enqueue(attendance_marking.get_shifts(), queue="long", timeout=4000) \ No newline at end of file