feat:overtime

This commit is contained in:
Anurag Mishra
2021-06-02 15:57:58 +05:30
parent e4fd6d7763
commit 3851d15360
21 changed files with 656 additions and 30 deletions

View File

@@ -31,18 +31,19 @@ class EmployeeCheckin(Document):
def fetch_shift(self):
shift_actual_timings = get_actual_start_end_datetime_of_shift(self.employee, get_datetime(self.time), True)
if shift_actual_timings[0] and shift_actual_timings[1]:
if shift_actual_timings[2].shift_type.determine_check_in_and_check_out == 'Strictly based on Log Type in Employee Checkin' and not self.log_type and not self.skip_auto_attendance:
frappe.throw(_('Log Type is required for check-ins falling in the shift: {0}.').format(shift_actual_timings[2].shift_type.name))
if not self.attendance:
self.shift = shift_actual_timings[2].shift_type.name
self.shift_actual_start = shift_actual_timings[0]
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
if not frappe.db.get_value("Shift Type", shift_actual_timings[2].shift_type.name, "allow_overtime"):
if shift_actual_timings[0] and shift_actual_timings[1]:
if shift_actual_timings[2].shift_type.determine_check_in_and_check_out == 'Strictly based on Log Type in Employee Checkin' and not self.log_type and not self.skip_auto_attendance:
frappe.throw(_('Log Type is required for check-ins falling in the shift: {0}.').format(shift_actual_timings[2].shift_type.name))
if not self.attendance:
self.shift = shift_actual_timings[2].shift_type.name
self.shift_actual_start = shift_actual_timings[0]
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
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.
# #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
@@ -101,12 +102,18 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
employee_doc = frappe.get_doc('Employee', employee)
if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
print(working_hours)
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'
from erpnext.hr.doctype.shift_type.shift_type import convert_time_into_duration
working_time = convert_time_into_duration(working_timedelta)
print("working")
print(working_timedelta)
print(working_time)
doc_dict = {
'doctype': 'Attendance',
@@ -114,7 +121,6 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
'attendance_date': attendance_date,
'status': attendance_status,
'working_time': working_time,
'working_timedelta': working_timedelta,
'company': employee_doc.company,
'shift': shift,
'late_entry': late_entry,
@@ -151,6 +157,8 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
"""
total_hours = 0
in_time = out_time = None
print("Madar Chod")
print(logs)
if check_in_out_type == 'Alternating entries as IN and OUT during the same shift':
in_time = logs[0].time
if len(logs) >= 2:

View File

@@ -19,7 +19,6 @@ from datetime import timedelta
class ShiftType(Document):
def validate(self):
self.validate_overtime()
self.set_working_hours()
@@ -35,12 +34,7 @@ class ShiftType(Document):
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"
self.standard_working_time = convert_time_into_duration(time_difference)
@@ -65,12 +59,17 @@ class ShiftType(Document):
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'])):
if self.allow_overtime == 1:
print("chumma")
checkins_log = itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_start']))
else:
checkins_log = itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start']))
for key, group in checkins_log:
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)
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)
@@ -87,6 +86,7 @@ class ShiftType(Document):
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)
print(total_working_hours)
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
@@ -95,6 +95,7 @@ class ShiftType(Document):
early_exit = True
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
print("------->>", 'Here', print(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:
@@ -173,3 +174,8 @@ def get_filtered_date_list(employee, start_date, end_date, filter_attendance=Tru
{"employee":employee, "start_date":start_date, "end_date":end_date, "holiday_list":holiday_list}, as_list=True)
return [getdate(date[0]) for date in dates]
def convert_time_into_duration(time_difference):
time_difference = str(time_difference).split(":")
return (int(time_difference[0]) * 3600) + (int(time_difference[1]) * 60) + int(time_difference[2])

View File

@@ -235,17 +235,12 @@ 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):
salary_struct = frappe.get_list("Salary Structure Assignment", filters = {
salary_structure_assignment = frappe.get_list("Salary Structure Assignment", filters = {
"employee": employee, 'docstatus': 1
},
fields=["from_date", "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))
return salary_structure_assignment[0].salary_structure if len(salary_structure_assignment) else None
def get_last_salary_slip(employee):
return frappe.get_list("Salary Slip", filters = {

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Overtime Details', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,101 @@
{
"actions": [],
"creation": "2021-05-27 13:39:56.788736",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"reference_document_type",
"reference_document",
"date",
"start_time",
"end_time",
"overtime_type",
"total_working_time",
"working_timedelta",
"overtime_duration",
"overtime_durationtime",
"overtime_amount"
],
"fields": [
{
"fieldname": "reference_document_type",
"fieldtype": "Link",
"label": "Reference Document Type ",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "date",
"fieldtype": "Date",
"label": "Date"
},
{
"fieldname": "start_time",
"fieldtype": "Datetime",
"label": "Start Time "
},
{
"fieldname": "end_time",
"fieldtype": "Datetime",
"label": "End Time"
},
{
"fieldname": "overtime_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Overtime Type ",
"options": "Overtime Type",
"reqd": 1
},
{
"fieldname": "total_working_time",
"fieldtype": "Data",
"label": "Total Working Time"
},
{
"default": "00:00:00",
"fieldname": "working_timedelta",
"fieldtype": "Time",
"label": "Working Time(Delta)"
},
{
"fieldname": "overtime_duration",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Overtime Duration",
"reqd": 1
},
{
"default": "00:00:00",
"fieldname": "overtime_durationtime",
"fieldtype": "Time",
"hidden": 1,
"label": "Overtime Duration(Time)"
},
{
"fieldname": "overtime_amount",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Overtime Amount",
"reqd": 1
},
{
"fieldname": "reference_document",
"fieldtype": "Dynamic Link",
"label": "Reference Document",
"options": "reference_document_type"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-27 13:43:11.578682",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Overtime Details",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class OvertimeDetails(Document):
pass

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import unittest
class TestOvertimeDetails(unittest.TestCase):
pass

View File

@@ -0,0 +1,33 @@
{
"actions": [],
"creation": "2021-05-25 12:49:03.287694",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"salary_component"
],
"fields": [
{
"fieldname": "salary_component",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Salary Component ",
"options": "Salary Component",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-25 12:49:03.287694",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Overtime Salary Component",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class OvertimeSalaryComponent(Document):
pass

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Overtime Slip', {
onload: function() {
},
employee: function(frm) {
if (frm.doc.employee) {
frm.events.set_frequency_and_dates(frm);
frm.events.get_emp_details_and_overtime_duration(frm);
}
},
from_date: function(frm) {
if (frm.doc.employee) {
frm.events.set_frequency_and_dates(frm);
frm.events.get_emp_details_and_overtime_duration(frm);
}
},
set_frequency_and_dates: function(frm) {
frappe.call({
method: "erpnext.payroll.doctype.overtime_slip.overtime_slip.get_frequency_and_dates",
args: {
employee: frm.doc.employee,
date: frm.doc.from_date || frm.doc.posting_date,
},
callback: function(r) {
frm.set_value("payroll_frequency", r.message[1]);
frm.doc.from_date = r.message[0].start_date;
frm.doc.to_date = r.message[0].end_date;
frm.refresh();
}
});
},
get_emp_details_and_overtime_duration: function(frm) {
if (frm.doc.employee) {
return frappe.call({
method: 'get_emp_and_overtime_details',
doc: frm.doc,
callback: function(r) {
}
});
}
},
reset_value: function(frm) {
}
});

View File

@@ -0,0 +1,181 @@
{
"actions": [],
"creation": "2021-05-27 12:47:32.372698",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"posting_date",
"employee",
"employee_name",
"column_break_4",
"status",
"company",
"department",
"section_break_7",
"from_date",
"to_date",
"column_break_10",
"payroll_frequency",
"section_break_12",
"overtime_details",
"section_break_13",
"total_overtime_duration",
"total_overtime_durationtime",
"column_break_17",
"amount",
"name1",
"amended_from"
],
"fields": [
{
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee",
"reqd": 1
},
{
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee Name",
"read_only": 1
},
{
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"label": "Department",
"options": "Department",
"read_only": 1
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1
},
{
"default": "Pending",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Pending\nApproved\nRejected",
"reqd": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Overtime Slip",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "overtime_details",
"fieldtype": "Table",
"label": "Overtime Details",
"options": "Overtime Details",
"reqd": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date"
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"read_only": 1
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "payroll_frequency",
"fieldtype": "Select",
"label": "Payroll Frequency",
"options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily"
},
{
"fieldname": "section_break_13",
"fieldtype": "Section Break"
},
{
"fieldname": "total_overtime_duration",
"fieldtype": "Data",
"label": "Total Overtime Duration"
},
{
"fieldname": "total_overtime_durationtime",
"fieldtype": "Time",
"label": "Total Overtime Duration(Time)"
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount"
},
{
"fieldname": "section_break_12",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_17",
"fieldtype": "Column Break"
},
{
"fieldname": "name1",
"fieldtype": "Duration",
"label": "name"
},
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-05-31 15:07:39.485473",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Overtime Slip",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,61 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.utils import get_datetime
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
from erpnext.payroll.doctype.gratuity.gratuity import get_salary_structure
from frappe.model.document import Document
from pprint import pprint
class OvertimeSlip(Document):
@frappe.whitelist()
def get_emp_and_overtime_details(self):
overtime_based_on = frappe.db.get_single_value("Payroll Settings", "overtime_based_on")
records = []
if overtime_based_on == "Attendance":
records = self.get_attendance_record()
elif overtime_based_on == "Timesheet":
records = self.get_timesheet_record()
else:
frappe.throw(_('Select "Calculate Overtime Hours Based On" in Payroll Settings'))
if len(records):
self.create_overtime_details_row(records)
else:
frappe.throw(_("No {0} records found for Overtime").format(overtime_based_on))
def create_overtime_details_row(self, records):
pprint(records)
def get_attendance_record(self):
records = frappe.db.sql("""SELECT overtime_duration, employee, name, attendance_date, overtime_type
FROM `TabAttendance`
WHERE
attendance_date >= %s AND attendance_date <= %s
AND employee = %s
AND docstatus = 1 AND status= 'Present'
AND (
overtime_duration IS NOT NULL OR overtime_duration != '00:00:00.000000'
)
""", (get_datetime(self.from_date), get_datetime(self.to_date), self.employee), as_dict=1)
return records
@frappe.whitelist()
def get_frequency_and_dates(employee, date):
print(date)
salary_structure = get_salary_structure(employee)
if salary_structure:
payroll_frequency = frappe.db.get_value('Salary Structure', salary_structure, 'payroll_frequency')
date_details = get_start_end_dates(payroll_frequency, date, frappe.db.get_value('Employee', employee, 'company'))
print(date_details)
return [date_details, payroll_frequency]
else:
frappe.throw(_("No Salary Structure Assignment found for Employee: {0}").format(employee))

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import unittest
class TestOvertimeSlip(unittest.TestCase):
pass

View File

@@ -0,0 +1,15 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Overtime Type', {
setup: function(frm) {
frm.set_query("party_type", () => {
let party_type = ["Employee", "Department", "Employee Grade"];
return {
filters: {
name: ["in", party_type]
}
};
});
}
});

View File

@@ -0,0 +1,116 @@
{
"actions": [],
"autoname": "Prompt",
"creation": "2021-05-25 12:49:09.178306",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"party_type",
"party",
"column_break_3",
"applicable_salary_component",
"pay_rate_multipliers_section",
"standard_multiplier",
"applicable_for_weekend",
"weekend_multiplier",
"column_break_9",
"applicable_for_public_holiday",
"public_holiday_multipliers"
],
"fields": [
{
"fieldname": "party_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Party Type",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"label": "Party",
"options": "party_type"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "applicable_salary_component",
"fieldtype": "Table MultiSelect",
"label": "Applicable Salary Component",
"options": "Overtime Salary Component"
},
{
"description": "Pay Rate Multipliers apply to the hourly wage for the position you\u2019re working during the overtime hours.",
"fieldname": "pay_rate_multipliers_section",
"fieldtype": "Section Break",
"label": "Pay Rate Multipliers"
},
{
"fieldname": "standard_multiplier",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Standard Multiplier",
"reqd": 1
},
{
"default": "0",
"description": "If unchecked, the standard multiplier will be taken as default for the weekend.\n",
"fieldname": "applicable_for_weekend",
"fieldtype": "Check",
"label": "Applicable for Weekend"
},
{
"depends_on": "eval: doc.applicable_for_weekend == 1",
"fieldname": "weekend_multiplier",
"fieldtype": "Float",
"label": "Weekend Multiplier",
"mandatory_depends_on": "eval: doc.applicable_for_weekend == 1"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If unchecked, the standard multiplier will be taken as default for Public Holiday.",
"fieldname": "applicable_for_public_holiday",
"fieldtype": "Check",
"label": "Applicable for Public Holiday"
},
{
"depends_on": "eval: doc.applicable_for_public_holiday == 1",
"fieldname": "public_holiday_multipliers",
"fieldtype": "Float",
"label": "Public Holiday Multipliers",
"mandatory_depends_on": "eval: doc.applicable_for_public_holiday == 1"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-05-25 13:21:11.318945",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Overtime Type",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class OvertimeType(Document):
pass

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import unittest
class TestOvertimeType(unittest.TestCase):
pass