mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-09 00:01:18 +00:00
feat: Overtime
This commit is contained in:
@@ -1,15 +1,67 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
cur_frm.add_fetch('employee', 'company', 'company');
|
||||
cur_frm.add_fetch('employee', 'employee_name', 'employee_name');
|
||||
frappe.ui.form.on('Attendance', {
|
||||
onload: function(frm) {
|
||||
|
||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
if(doc.__islocal) cur_frm.set_value("attendance_date", frappe.datetime.get_today());
|
||||
}
|
||||
frappe.db.get_single_value("Payroll Settings", "fetch_standard_working_hours_from_shift_type").then((r)=>{
|
||||
if (!r) {
|
||||
// for not fetching from Shift Type
|
||||
delete cur_frm.fetch_dict["shift"];
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
if (frm.doc.__islocal) {
|
||||
cur_frm.set_value("attendance_date", frappe.datetime.get_today());
|
||||
}
|
||||
|
||||
frm.set_query("employee", ()=>{
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frm.events.set_shift(frm);
|
||||
frm.events.set_overtime_type(frm);
|
||||
}
|
||||
},
|
||||
|
||||
set_shift: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.attendance.attendance.get_shift_type",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
attendance_date: frm.doc.attendance_date
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value("shift", r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_overtime_type: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.attendance.attendance.get_overtime_type",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value("overtime_type", r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
overtime_duration: function(frm) {
|
||||
let duration = frm.doc.overtime_duration.split(":");
|
||||
let overtime_duration_words = duration[0] + " Hours " + duration[1] + " Minutes";
|
||||
frm.set_value("overtime_duration_words", overtime_duration_words);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"naming_series",
|
||||
"employee",
|
||||
"employee_name",
|
||||
"working_hours",
|
||||
"status",
|
||||
"leave_type",
|
||||
"leave_application",
|
||||
@@ -20,13 +19,22 @@
|
||||
"company",
|
||||
"department",
|
||||
"attendance_request",
|
||||
"details_section",
|
||||
"shift_details_section",
|
||||
"shift",
|
||||
"in_time",
|
||||
"out_time",
|
||||
"column_break_18",
|
||||
"standard_working_time",
|
||||
"standard_working_time_delta",
|
||||
"working_time",
|
||||
"working_timedelta",
|
||||
"late_entry",
|
||||
"early_exit",
|
||||
"overtime_details_section",
|
||||
"overtime_type",
|
||||
"overtime_duration",
|
||||
"column_break_27",
|
||||
"overtime_duration_words",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@@ -69,14 +77,6 @@
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "working_hours",
|
||||
"fieldname": "working_hours",
|
||||
"fieldtype": "Float",
|
||||
"label": "Working Hours",
|
||||
"precision": "1",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Present",
|
||||
"fieldname": "status",
|
||||
@@ -125,6 +125,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
@@ -146,7 +147,8 @@
|
||||
"fieldname": "shift",
|
||||
"fieldtype": "Link",
|
||||
"label": "Shift",
|
||||
"options": "Shift Type"
|
||||
"options": "Shift Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "attendance_request",
|
||||
@@ -177,11 +179,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Early Exit"
|
||||
},
|
||||
{
|
||||
"fieldname": "details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "shift",
|
||||
"fieldname": "in_time",
|
||||
@@ -199,13 +196,78 @@
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "overtime_type",
|
||||
"fieldname": "overtime_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Overtime Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "overtime_type",
|
||||
"fieldname": "overtime_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Overtime Type",
|
||||
"options": "Overtime Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Shift duration for a day",
|
||||
"fetch_from": "shift.standard_working_time",
|
||||
"fieldname": "standard_working_time",
|
||||
"fieldtype": "Data",
|
||||
"label": " Standard Working Time",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "shift.working_time_delta",
|
||||
"fieldname": "standard_working_time_delta",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 1,
|
||||
"label": "Standard Working Time(Delta)"
|
||||
},
|
||||
{
|
||||
"depends_on": "working_time",
|
||||
"fieldname": "working_time",
|
||||
"fieldtype": "Data",
|
||||
"label": "Total Working Time",
|
||||
"precision": "1",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "working_timedelta",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 1,
|
||||
"label": "Working Time(Delta)"
|
||||
},
|
||||
{
|
||||
"fieldname": "overtime_duration_words",
|
||||
"fieldtype": "Data",
|
||||
"label": "Overtime Duration(Words)",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "00:00:00",
|
||||
"fieldname": "overtime_duration",
|
||||
"fieldtype": "Time",
|
||||
"label": "Overtime Duration"
|
||||
},
|
||||
{
|
||||
"depends_on": "shift",
|
||||
"fieldname": "shift_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shift Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_27",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-ok",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified": "2021-05-26 16:44:33.219313",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Attendance",
|
||||
|
||||
@@ -7,8 +7,8 @@ import frappe
|
||||
from frappe.utils import getdate, nowdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr, get_datetime, formatdate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
from frappe.utils import cstr, get_datetime, formatdate, getdate
|
||||
|
||||
class Attendance(Document):
|
||||
def validate(self):
|
||||
@@ -19,6 +19,8 @@ class Attendance(Document):
|
||||
self.validate_duplicate_record()
|
||||
self.validate_employee_status()
|
||||
self.check_leave_record()
|
||||
self.set_overtime_type()
|
||||
self.set_default_shift()
|
||||
|
||||
def validate_attendance_date(self):
|
||||
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
|
||||
@@ -45,6 +47,13 @@ class Attendance(Document):
|
||||
if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
|
||||
frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))
|
||||
|
||||
def set_default_shift(self):
|
||||
if not self.shift:
|
||||
self.shift = get_shift_type(self.employee, self.attendance_date)
|
||||
|
||||
def set_overtime_type(self):
|
||||
self.overtime_type = get_overtime_type(self.employee)
|
||||
|
||||
def check_leave_record(self):
|
||||
leave_record = frappe.db.sql("""
|
||||
select leave_type, half_day, half_day_date
|
||||
@@ -80,6 +89,63 @@ class Attendance(Document):
|
||||
if not emp:
|
||||
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
|
||||
|
||||
def calculate_overtime_duration(self):
|
||||
#this method is only for Calculation of overtime based on Attendance through Employee Checkins
|
||||
overtime_duration = self.working_timedelta - self.standard_working_time_delta
|
||||
self.overtime_duration = overtime_duration
|
||||
overtime_duration = str(overtime_duration).split(':')
|
||||
if int(overtime_duration[0]) or int(overtime_duration[1]):
|
||||
self.overtime_duration_words = overtime_duration[0] + " Hours " + overtime_duration[1] + " Minutes"
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shift_type(employee, attendance_date):
|
||||
emp_shift = frappe.db.get_value("Employee", employee, "default_shift")
|
||||
|
||||
shift_assignment = frappe.db.sql('''SELECT name, shift_type
|
||||
FROM
|
||||
`tabShift Assignment`
|
||||
WHERE
|
||||
docstatus = 1
|
||||
AND employee = %(employee)s AND start_date <= %(attendance_date)s
|
||||
AND (end_date >= %(attendance_date)s OR end_date IS null)
|
||||
AND status = "Active"
|
||||
''', {
|
||||
"employee": employee,
|
||||
"attendance_date": attendance_date,
|
||||
}, as_dict = 1)
|
||||
|
||||
if len(shift_assignment):
|
||||
shift = shift_assignment[0].shift_type
|
||||
else:
|
||||
shift = emp_shift
|
||||
|
||||
return shift
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_overtime_type(employee):
|
||||
overtime_based_on = frappe.db.get_single_value("Payroll Settings", "overtime_based_on")
|
||||
if overtime_based_on == "Attendance":
|
||||
|
||||
emp_department = frappe.db.get_value("Employee", employee, "department")
|
||||
if emp_department:
|
||||
overtime_type = frappe.get_list("Overtime Type", filters={"party_type": "Department", "party": emp_department}, fields=['name'])
|
||||
if len(overtime_type):
|
||||
overtime_type = overtime_type[0].name
|
||||
|
||||
emp_grade = frappe.db.get_value("Employee", employee, "grade")
|
||||
if emp_grade:
|
||||
overtime_type = frappe.get_list("Overtime Type", filters={"party_type": "Employee Grade", "party": emp_grade},
|
||||
fields=['name'])
|
||||
if len(overtime_type):
|
||||
|
||||
overtime_type = overtime_type[0].name
|
||||
|
||||
overtime_type = frappe.get_list("Overtime Type", filters={"party_type": "Employee", "party": employee}, fields=['name'])
|
||||
if len(overtime_type):
|
||||
overtime_type = overtime_type[0].name
|
||||
|
||||
return overtime_type
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
events = []
|
||||
|
||||
@@ -6,6 +6,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import now, cint, get_datetime
|
||||
from frappe.model.document import Document
|
||||
from datetime import timedelta
|
||||
from math import modf
|
||||
from frappe import _
|
||||
|
||||
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift
|
||||
@@ -38,8 +40,12 @@ class EmployeeCheckin(Document):
|
||||
self.shift_actual_end = shift_actual_timings[1]
|
||||
self.shift_start = shift_actual_timings[2].start_datetime
|
||||
self.shift_end = shift_actual_timings[2].end_datetime
|
||||
else:
|
||||
self.shift = None
|
||||
elif frappe.db.get_value("Shift Type", shift_actual_timings[2].shift_type.name, "allow_overtime"):
|
||||
#because after Actual time it takes check-in/out invalid
|
||||
#if employee checkout late or check-in before before shift timing adding time buffer.
|
||||
self.shift = shift_actual_timings[2].shift_type.name
|
||||
self.shift_start = shift_actual_timings[2].start_datetime
|
||||
self.shift_end = shift_actual_timings[2].end_datetime
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=None, log_type=None, skip_auto_attendance=0, employee_fieldname='attendance_device_id'):
|
||||
@@ -56,7 +62,8 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
|
||||
if not employee_field_value or not timestamp:
|
||||
frappe.throw(_("'employee_field_value' and 'timestamp' are required."))
|
||||
|
||||
employee = frappe.db.get_values("Employee", {employee_fieldname: employee_field_value}, ["name", "employee_name", employee_fieldname], as_dict=True)
|
||||
employee = frappe.db.get_values("Employee", {employee_fieldname: employee_field_value},
|
||||
["name", "employee_name", employee_fieldname], as_dict=True)
|
||||
if employee:
|
||||
employee = employee[0]
|
||||
else:
|
||||
@@ -93,12 +100,21 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
||||
elif attendance_status in ('Present', 'Absent', 'Half Day'):
|
||||
employee_doc = frappe.get_doc('Employee', employee)
|
||||
if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
|
||||
|
||||
working_timedelta = '00:00:00'
|
||||
working_time = None
|
||||
working_time = modf(working_hours)
|
||||
if working_time[1] or working_time[0]:
|
||||
working_timedelta = timedelta(hours =int(working_time[1]), minutes = int(working_time[0] * 60))
|
||||
working_time = str(int(working_time[1])) + ' Hours ' + str(int(working_time[0] * 60)) + ' Minutes'
|
||||
|
||||
doc_dict = {
|
||||
'doctype': 'Attendance',
|
||||
'employee': employee,
|
||||
'attendance_date': attendance_date,
|
||||
'status': attendance_status,
|
||||
'working_hours': working_hours,
|
||||
'working_time': working_time,
|
||||
'working_timedelta': working_timedelta,
|
||||
'company': employee_doc.company,
|
||||
'shift': shift,
|
||||
'late_entry': late_entry,
|
||||
@@ -106,7 +122,11 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
||||
'in_time': in_time,
|
||||
'out_time': out_time
|
||||
}
|
||||
|
||||
attendance = frappe.get_doc(doc_dict).insert()
|
||||
if frappe.db.get_value("Shift type", shift, "allow_overtime"):
|
||||
attendance.calculate_overtime_duration()
|
||||
attendance.save()
|
||||
attendance.submit()
|
||||
frappe.db.sql("""update `tabEmployee Checkin`
|
||||
set attendance = %s
|
||||
@@ -121,10 +141,10 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
||||
frappe.throw(_('{} is an invalid Attendance Status.').format(attendance_status))
|
||||
|
||||
|
||||
|
||||
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
|
||||
Zero is returned for all invalid cases.
|
||||
|
||||
:param logs: The List of 'Employee Checkin'.
|
||||
:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
|
||||
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "prompt",
|
||||
"creation": "2018-04-13 16:22:52.954783",
|
||||
"doctype": "DocType",
|
||||
@@ -7,17 +8,22 @@
|
||||
"field_order": [
|
||||
"start_time",
|
||||
"end_time",
|
||||
"standard_working_time",
|
||||
"working_time_delta",
|
||||
"column_break_3",
|
||||
"holiday_list",
|
||||
"enable_auto_attendance",
|
||||
"allow_overtime",
|
||||
"auto_attendance_settings_section",
|
||||
"determine_check_in_and_check_out",
|
||||
"working_hours_calculation_based_on",
|
||||
"column_break_10",
|
||||
"begin_check_in_before_shift_start_time",
|
||||
"allow_check_out_after_shift_end_time",
|
||||
"column_break_10",
|
||||
"section_break_15",
|
||||
"working_hours_threshold_for_half_day",
|
||||
"working_hours_threshold_for_absent",
|
||||
"column_break_19",
|
||||
"process_attendance_after",
|
||||
"last_sync_of_checkin",
|
||||
"grace_period_settings_auto_attendance_section",
|
||||
@@ -29,6 +35,7 @@
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "00:00:00",
|
||||
"fieldname": "start_time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
@@ -36,6 +43,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "00:00:00",
|
||||
"fieldname": "end_time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
@@ -84,6 +92,7 @@
|
||||
},
|
||||
{
|
||||
"default": "60",
|
||||
"depends_on": "eval: doc.allow_overtime == 0",
|
||||
"description": "The time before the shift start time during which Employee Check-in is considered for attendance.",
|
||||
"fieldname": "begin_check_in_before_shift_start_time",
|
||||
"fieldtype": "Int",
|
||||
@@ -121,6 +130,7 @@
|
||||
},
|
||||
{
|
||||
"default": "60",
|
||||
"depends_on": "eval: doc.allow_overtime == 0",
|
||||
"description": "Time after the end of shift during which check-out is considered for attendance.",
|
||||
"fieldname": "allow_check_out_after_shift_end_time",
|
||||
"fieldtype": "Int",
|
||||
@@ -156,9 +166,39 @@
|
||||
"fieldname": "last_sync_of_checkin",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Last Sync of Checkin"
|
||||
},
|
||||
{
|
||||
"fieldname": "standard_working_time",
|
||||
"fieldtype": "Data",
|
||||
"label": "Standard Working Time",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "working_time_delta",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 1,
|
||||
"label": "Working time(delta)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "enable_auto_attendance",
|
||||
"description": "Overtime will be calculated and Overtime Duration will reflect in attendance records. Check Payroll Settings for more options. ",
|
||||
"fieldname": "allow_overtime",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Overtime"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_auto_attendance",
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"modified": "2019-07-30 01:05:24.660666",
|
||||
"links": [],
|
||||
"modified": "2021-05-26 14:10:09.574202",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Shift Type",
|
||||
|
||||
@@ -7,14 +7,50 @@ import itertools
|
||||
from datetime import timedelta
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from math import modf
|
||||
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_attendance
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from datetime import timedelta
|
||||
|
||||
class ShiftType(Document):
|
||||
def validate(self):
|
||||
|
||||
self.validate_overtime()
|
||||
self.set_working_hours()
|
||||
|
||||
def set_working_hours(self):
|
||||
end_time = self.end_time.split(":")
|
||||
start_time = self.start_time.split(":")
|
||||
|
||||
shift_end = timedelta(hours = int(end_time[0]), minutes = int(end_time[1]), seconds = int(end_time[2]))
|
||||
shift_start = timedelta(hours =int(start_time[0]), minutes = int(start_time[1]), seconds = int(start_time[2]))
|
||||
|
||||
if shift_end > shift_start:
|
||||
time_difference = shift_end - shift_start
|
||||
else:
|
||||
# for night shift
|
||||
time_difference = shift_start - shift_end
|
||||
|
||||
self.working_time_delta = str(time_difference)
|
||||
time_difference = str(time_difference).split(":")
|
||||
|
||||
if int(time_difference[0]) or int(time_difference[1]):
|
||||
self.standard_working_time = time_difference[0] + " Hours " + time_difference[1] + " Minutes"
|
||||
|
||||
|
||||
|
||||
def validate_overtime(self):
|
||||
if not frappe.db.get_single_value("Payroll Settings", "fetch_standard_working_hours_from_shift_type") and self.allow_overtime:
|
||||
frappe.throw(_('Please enable "Fetch Standard Working Hours from Shift Type" in payroll Settings for Overtime.'))
|
||||
|
||||
if frappe.db.get_single_value("Payroll Settings", "overtime_based_on") != "Attendance" and self.allow_overtime:
|
||||
frappe.throw(_('Please set Overtime based on "Attendance" in payroll Settings for Overtime.'))
|
||||
|
||||
@frappe.whitelist()
|
||||
def process_auto_attendance(self):
|
||||
if not cint(self.enable_auto_attendance) or not self.process_attendance_after or not self.last_sync_of_checkin:
|
||||
@@ -27,10 +63,17 @@ class ShiftType(Document):
|
||||
'shift': self.name
|
||||
}
|
||||
logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
|
||||
from pprint import pprint
|
||||
pprint(logs)
|
||||
for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
|
||||
|
||||
single_shift_logs = list(group)
|
||||
attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs)
|
||||
|
||||
print("_______>>>>>>>>>>>>>",attendance_status, working_hours, late_entry, early_exit, in_time, out_time)
|
||||
|
||||
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name)
|
||||
|
||||
for employee in self.get_assigned_employee(self.process_attendance_after, True):
|
||||
self.mark_absent_for_dates_with_no_attendance(employee)
|
||||
|
||||
@@ -41,8 +84,10 @@ class ShiftType(Document):
|
||||
1. These logs belongs to an single shift, single employee and is not in a holiday date.
|
||||
2. Logs are in chronological order
|
||||
"""
|
||||
|
||||
late_entry = early_exit = False
|
||||
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
|
||||
|
||||
@@ -51,8 +96,10 @@ class ShiftType(Document):
|
||||
|
||||
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, in_time, out_time
|
||||
|
||||
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
|
||||
return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time
|
||||
|
||||
return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time
|
||||
|
||||
def mark_absent_for_dates_with_no_attendance(self, employee):
|
||||
|
||||
@@ -235,11 +235,17 @@ def get_gratuity_rule_slabs(gratuity_rule):
|
||||
return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx")
|
||||
|
||||
def get_salary_structure(employee):
|
||||
return frappe.get_list("Salary Structure Assignment", filters = {
|
||||
salary_struct = frappe.get_list("Salary Structure Assignment", filters = {
|
||||
"employee": employee, 'docstatus': 1
|
||||
},
|
||||
fields=["from_date", "salary_structure"],
|
||||
order_by = "from_date desc")[0].salary_structure
|
||||
order_by = "from_date desc")
|
||||
|
||||
if len(salary_struct):
|
||||
return salary_struct[0].salary_structure
|
||||
else:
|
||||
frappe.throw(_("No Salary Structure Assignment found for employee: {0}").format(employee))
|
||||
|
||||
|
||||
def get_last_salary_slip(employee):
|
||||
return frappe.get_list("Salary Slip", filters = {
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
"password_policy",
|
||||
"section_break_12",
|
||||
"overtime_based_on",
|
||||
"maximum_overtime_hours_allowed",
|
||||
"overtime_salary_component",
|
||||
"column_break_14",
|
||||
"fetch_standard_working_hours_from_shift_type"
|
||||
"fetch_standard_working_hours_from_shift_type",
|
||||
"is_overtime_approval_required"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -105,7 +107,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_12",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Overtime Calculation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -124,13 +127,25 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Calculate Overtime Hours Based On",
|
||||
"options": "Attendance\nTimesheet"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_overtime_approval_required",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Overtime Approval Required"
|
||||
},
|
||||
{
|
||||
"description": "Overtime payment will not be given for more than the defined hours limit.",
|
||||
"fieldname": "maximum_overtime_hours_allowed",
|
||||
"fieldtype": "Int",
|
||||
"label": "Maximum Overtime Hours Allowed For Payment "
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-10 12:56:08.161319",
|
||||
"modified": "2021-05-26 15:56:25.313007",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Payroll Settings",
|
||||
|
||||
Reference in New Issue
Block a user