fix: some enhancemets and test cases

This commit is contained in:
Anurag Mishra
2021-06-18 15:43:12 +05:30
parent 769d774ccc
commit 73b3121127
16 changed files with 323 additions and 64 deletions

View File

@@ -135,26 +135,26 @@ def get_shift_type(employee, attendance_date):
@frappe.whitelist() @frappe.whitelist()
def get_overtime_type(employee): def get_overtime_type(employee):
overtime_type = None
emp_department = frappe.db.get_value("Employee", employee, "department") emp_department = frappe.db.get_value("Employee", employee, "department")
if emp_department: if emp_department:
overtime_type = frappe.get_list("Overtime Type", filters={ overtime_type_doc = frappe.get_list("Overtime Type", filters={
"applicable_for": "Department", "department": emp_department}, fields=['name']) "applicable_for": "Department", "department": emp_department}, fields=['name'])
if len(overtime_type): if len(overtime_type_doc):
overtime_type = overtime_type[0].name overtime_type = overtime_type_doc[0].name
emp_grade = frappe.db.get_value("Employee", employee, "grade") emp_grade = frappe.db.get_value("Employee", employee, "grade")
if emp_grade: if emp_grade:
overtime_type = frappe.get_list("Overtime Type", filters={ overtime_type_doc = frappe.get_list("Overtime Type", filters={
"applicable_for": "Employee Grade", "employee_grade": emp_grade}, "applicable_for": "Employee Grade", "employee_grade": emp_grade},
fields=['name']) fields=['name'])
if len(overtime_type): if len(overtime_type_doc):
overtime_type = overtime_type_doc[0].name
overtime_type = overtime_type[0].name overtime_type_doc = frappe.get_list("Overtime Type", filters={
overtime_type = frappe.get_list("Overtime Type", filters={
"applicable_for": "Employee", "employee": employee}, fields=['name']) "applicable_for": "Employee", "employee": employee}, fields=['name'])
if len(overtime_type): if len(overtime_type_doc):
overtime_type = overtime_type[0].name overtime_type = overtime_type_doc[0].name
return overtime_type return overtime_type

View File

@@ -102,7 +102,6 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
employee_doc = frappe.get_doc('Employee', employee) employee_doc = frappe.get_doc('Employee', employee)
if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}): if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
print(working_hours)
working_timedelta = '00:00:00' working_timedelta = '00:00:00'
working_time = None working_time = None
working_time = modf(working_hours) working_time = modf(working_hours)
@@ -153,8 +152,6 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
""" """
total_hours = 0 total_hours = 0
in_time = out_time = None 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': if check_in_out_type == 'Alternating entries as IN and OUT during the same shift':
in_time = logs[0].time in_time = logs[0].time
if len(logs) >= 2: if len(logs) >= 2:

View File

@@ -42,11 +42,11 @@ class TestEmployeeCheckin(unittest.TestCase):
self.assertEqual(logs_count, 4) self.assertEqual(logs_count, 4)
attendance_count = frappe.db.count('Attendance', {'status':'Present', 'working_hours':8.2, attendance_count = frappe.db.count('Attendance', {'status':'Present', 'working_hours':8.2,
'employee':employee, 'attendance_date':now_date}) 'employee':employee, 'attendance_date':now_date})
self.assertEqual(attendance_count, 1) self.assertEqual(attendance_count, 1)
def test_calculate_working_hours(self): def test_calculate_working_hours(self):
check_in_out_type = ['Alternating entries as IN and OUT during the same shift', check_in_out_type = ['Alternating entries as IN and OUT during the same shift',
'Strictly based on Log Type in Employee Checkin'] 'Strictly based on Log Type in Employee Checkin']
working_hours_calc_type = ['First Check-in and Last Check-out', working_hours_calc_type = ['First Check-in and Last Check-out',
'Every Valid Check-in and Check-out'] 'Every Valid Check-in and Check-out']
logs_type_1 = [ logs_type_1 = [
@@ -88,12 +88,12 @@ def make_n_checkins(employee, n, hours_to_reverse=1):
return logs return logs
def make_checkin(employee, time=now_datetime()): def make_checkin(employee, time=now_datetime(), log_type = "IN"):
log = frappe.get_doc({ log = frappe.get_doc({
"doctype": "Employee Checkin", "doctype": "Employee Checkin",
"employee" : employee, "employee" : employee,
"time" : time, "time" : time,
"device_id" : "device1", "device_id" : "device1",
"log_type" : "IN" "log_type" : log_type
}).insert() }).insert()
return log return log

View File

@@ -24,7 +24,7 @@ class ShiftType(Document):
start_time = self.start_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_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])) shift_start = timedelta(hours = int(start_time[0]), minutes = int(start_time[1]), seconds = int(start_time[2]))
if shift_end > shift_start: if shift_end > shift_start:
time_difference = shift_end - shift_start time_difference = shift_end - shift_start

View File

@@ -8,3 +8,13 @@ import unittest
class TestShiftType(unittest.TestCase): class TestShiftType(unittest.TestCase):
pass pass
def create_shift_type():
shift_type = frappe.new_doc("Shift Type")
shift_type.name = "test shift"
shift_type.start_time = "9:00:00"
shift_type.end_time = "18:00:00"
shift_type.enable_auto_attendance = 1
shift_type.save()
return shift_type

View File

@@ -20,7 +20,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-05-25 12:49:03.287694", "modified": "2021-06-16 14:48:59.476787",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Overtime Salary Component", "name": "Overtime Salary Component",

View File

@@ -148,7 +148,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-06-15 12:53:17.355755", "modified": "2021-06-16 14:55:13.441709",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Overtime Slip", "name": "Overtime Slip",
@@ -162,9 +162,32 @@
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "HR Manager",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"share": 1
} }
], ],
"sort_field": "modified", "sort_field": "modified",

View File

@@ -9,14 +9,18 @@ from erpnext.payroll.doctype.gratuity.gratuity import get_salary_structure
from frappe.model.document import Document from frappe.model.document import Document
class OvertimeSlip(Document): class OvertimeSlip(Document):
def validate(self): def validate(self):
if not (self.from_date or self.to_date or self.payroll_frequency):
date = self.from_date or self.posting_date
data = get_frequency_and_dates(self.employee, date)
self.from_date = data[0].start_date
self.to_date = data[0].end_date
self.payroll_frequency = data[1]
self.validate_overlap() self.validate_overlap()
if self.from_date >= self.to_date: if self.from_date >= self.to_date:
frappe.throw(_("From date can not be greater than To date")) frappe.throw(_("From date can not be greater than To date"))
if not (self.from_date or self.to_date or self.payroll_frequency):
date = self.from_date or self.posting_date
get_frequency_and_dates(self.employee, date)
if not len(self.overtime_details): if not len(self.overtime_details):
self.get_emp_and_overtime_details() self.get_emp_and_overtime_details()
@@ -29,7 +33,8 @@ class OvertimeSlip(Document):
"docstatus": ("<", 2), "docstatus": ("<", 2),
"employee": self.employee, "employee": self.employee,
"to_date": (">=", self.from_date), "to_date": (">=", self.from_date),
"from_date": ("<=", self.to_date) "from_date": ("<=", self.to_date),
"name": ("!=", self.name)
}) })
if len(overtime_slips): if len(overtime_slips):
form_link = get_link_to_form("Overtime Slip", overtime_slips[0].name) form_link = get_link_to_form("Overtime Slip", overtime_slips[0].name)
@@ -119,17 +124,13 @@ class OvertimeSlip(Document):
AND ( AND (
overtime_duration IS NOT NULL OR overtime_duration != '00:00:00.000000' overtime_duration IS NOT NULL OR overtime_duration != '00:00:00.000000'
) )
""", (getdate(self.from_date), getdate(self.to_date), self.employee), as_dict=1, debug = 1) """, (getdate(self.from_date), getdate(self.to_date), self.employee), as_dict=1)
return records return records
return [] return []
def get_timesheet_record(self): def get_timesheet_record(self):
if self.from_date and self.to_date: if self.from_date and self.to_date:
"""SELECT Orders.OrderID, Customers.CustomerName, Orders.OrderDate
FROM Orders
INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID;"""
records = frappe.db.sql("""SELECT ts.name, ts.start_date, ts.end_date, tsd.overtime_on, tsd.overtime_type, tsd.overtime_hours records = frappe.db.sql("""SELECT ts.name, ts.start_date, ts.end_date, tsd.overtime_on, tsd.overtime_type, tsd.overtime_hours
FROM `TabTimesheet` AS ts FROM `TabTimesheet` AS ts
INNER JOIN `tabTimesheet Detail` As tsd ON tsd.parent = ts.name INNER JOIN `tabTimesheet Detail` As tsd ON tsd.parent = ts.name
@@ -141,7 +142,7 @@ class OvertimeSlip(Document):
AND ( AND (
total_overtime_hours IS NOT NULL OR total_overtime_hours != 0 total_overtime_hours IS NOT NULL OR total_overtime_hours != 0
) )
""", {"from_date": get_datetime(self.from_date), "to_date": get_datetime(self.to_date),"employee": self.employee}, as_dict=1, debug = 1) """, {"from_date": get_datetime(self.from_date), "to_date": get_datetime(self.to_date),"employee": self.employee}, as_dict=1)
return records return records
return [] return []
@@ -156,7 +157,6 @@ def get_standard_working_hours(employee, date):
standard_working_time = 0 standard_working_time = 0
fetch_from_shift = frappe.db.get_single_value("Payroll Settings", "fetch_standard_working_hours_from_shift_type") fetch_from_shift = frappe.db.get_single_value("Payroll Settings", "fetch_standard_working_hours_from_shift_type")
if len(shift_assignment) and fetch_from_shift: if len(shift_assignment) and fetch_from_shift:

View File

@@ -1,8 +1,154 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import frappe
# import frappe from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.overtime_type.test_overtime_type import create_overtime_type
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
from erpnext.hr.doctype.shift_type.test_shift_type import create_shift_type
from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin
from frappe.utils import today, add_days, get_datetime
from datetime import timedelta
import unittest import unittest
class TestOvertimeSlip(unittest.TestCase): class TestOvertimeSlip(unittest.TestCase):
pass def tearDown(self):
for doctype in ["Overtime Type","Overtime Slip", "Attendance", "Employee Checkin", "Shift Type"]:
frappe.db.sql("DELETE FROM `tab{0}`".format(doctype))
frappe.db.sql("DELETE FROM `tabEmployee` WHERE user_id = 'test_employee@overtime.com'")
frappe.db.commit()
def test_overtime_based_on_attendance_without_shift_type(self):
frappe.db.set_value("Payroll Settings", None, "overtime_based_on", "Attendance")
frappe.db.set_value("Payroll Settings", None, "fetch_standard_working_hours_from_shift_type", 0)
frappe.db.set_value("HR Settings", None, "standard_working_hours", 7)
employee = make_employee("test_employee@overtime.com", company='_Test Company')
make_salary_structure("structure for Overtime", "Monthly", employee=employee)
overtime_type = create_overtime_type(employee=employee)
attendance_record = create_attendance_records_for_overtime(employee, overtime_type.name)
slip = create_overtime_slip(employee)
for detail in slip.overtime_details:
self.assertIn(detail.reference_document, attendance_record.keys())
if detail.reference_document in attendance_record.keys():
self.assertEqual(detail.overtime_duration, attendance_record[detail.reference_document]["overtime_duration"])
self.assertEqual(str(detail.date), attendance_record[detail.reference_document]["attendance_date"])
def test_overtime_based_on_attendance_with_shift_type_through_employee_checkins(self):
frappe.db.set_value("Payroll Settings", None, "overtime_based_on", "Attendance")
frappe.db.set_value("Payroll Settings", None, "fetch_standard_working_hours_from_shift_type", 1)
shift_type = create_shift_type()
shift_type.allow_overtime = 1
shift_type.process_attendance_after = add_days(today(), -1)
shift_type.last_sync_of_checkin = get_datetime(add_days(today(), 1))
shift_type.save()
employee = make_employee("test_employee@overtime.com", company='_Test Company')
make_salary_structure("structure for Overtime", "Monthly", employee=employee)
frappe.db.set_value("Employee", employee, "default_shift", shift_type.name)
checkin = make_checkin(employee, time = get_datetime(today()) + timedelta(hours=9))
checkout = make_checkin(employee, time = get_datetime(today()) + timedelta(hours=20), log_type="OUT")
self.assertEqual(checkin.shift, shift_type.name)
self.assertEqual(checkout.shift, shift_type.name)
create_overtime_type(employee=employee)
shift_type.process_auto_attendance()
checkin.reload()
attendance_records = frappe.get_all("Attendance", filters = {
'shift': shift_type.name, 'status': 'Present'
}, fields = ["name", "overtime_duration", "overtime_type", "attendance_date"])
records = {}
for record in attendance_records:
records[record.name] = {
"overtime_duration": record.overtime_duration,
"overtime_type": record.overtime_type,
"attendance_date": record.attendance_date
}
slip = create_overtime_slip(employee)
for detail in slip.overtime_details:
self.assertIn(detail.reference_document, records.keys())
if detail.reference_document in records.keys():
self.assertEqual(detail.overtime_duration, records[detail.reference_document]["overtime_duration"])
self.assertEqual(str(detail.date), str(records[detail.reference_document]["attendance_date"]))
def test_overtime_based_on_timesheet(self):
frappe.db.set_value("Payroll Settings", None, "overtime_based_on", "Timesheet")
frappe.db.set_value("HR Settings", None, "standard_working_hours", 7)
employee = make_employee("test_employee@overtime.com", company='_Test Company')
make_salary_structure("structure for Overtime", "Monthly", employee=employee)
overtime_type = create_overtime_type(employee=employee)
time_log, timesheet = create_timesheet_record_for_overtime(employee, overtime_type.name)
slip = create_overtime_slip(employee)
for detail in slip.overtime_details:
self.assertEqual(time_log.overtime_hours * 3600, detail.overtime_duration)
self.assertEqual(time_log.overtime_on, get_datetime(detail.date))
self.assertEqual(time_log.overtime_type, detail.overtime_type)
self.assertEqual(timesheet, detail.reference_document)
def create_attendance_records_for_overtime(employee, overtime_type):
records = {}
for x in range(2):
attendance = frappe.new_doc('Attendance')
attendance.employee = employee
attendance.status = 'Present'
attendance.attendance_date = add_days(today(), -(x))
attendance.overtime_type = overtime_type
attendance.overtime_duration = 2 * 3600 #for convertion to duration
attendance.save()
attendance.submit()
records[attendance.name] = {
"overtime_duration": attendance.overtime_duration,
"overtime_type": attendance.overtime_type,
"attendance_date": attendance.attendance_date
}
return records
def create_timesheet_record_for_overtime(employee, overtime_type):
timesheet =frappe.new_doc("Timesheet")
timesheet.employee = employee
timesheet.time_logs = []
time_log = {
"activity_type": "Planning",
"from_time": get_datetime(add_days(today(), -1)),
"to_time": get_datetime(add_days(today(), 2)),
"expected_hours": 48,
"hours": 48,
"is_overtime": 1,
"overtime_type": overtime_type,
"overtime_on": get_datetime(today()),
"overtime_hours": 7
}
timesheet.append("time_logs", time_log)
timesheet.save()
timesheet.submit()
return frappe._dict(time_log), timesheet.name
def create_overtime_slip(employee):
slip = frappe.new_doc("Overtime Slip")
slip.employee = employee
slip.overtime_details = []
slip.save()
return slip

View File

@@ -1,8 +1,42 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
# import frappe import frappe
import unittest import unittest
class TestOvertimeType(unittest.TestCase): class TestOvertimeType(unittest.TestCase):
pass pass
def create_overtime_type(**args):
args = frappe._dict(args)
overtime_type = frappe.new_doc("Overtime Type")
overtime_type.name = "Test Overtime"
overtime_type.applicable_for = args.applicable_for or "Employee"
if overtime_type.applicable_for == "Department":
overtime_type.department = args.department
elif overtime_type.applicable_for == "Employee Grade":
overtime_type.employee_grade = args.employee_grade
else:
overtime_type.employee = args.employee
overtime_type.standard_multiplier = 1.25
overtime_type.applicable_for_weekend = args.applicable_for_weekend or 0
overtime_type.applicable_for_public_holiday = args.applicable_for_public_holiday or 0
if args.applicable_for_weekend:
overtime_type.weekend_multiplier = 1.5
if args.applicable_for_public_holidays:
overtime_type.public_holiday_multiplier = 2
overtime_type.append("applicable_salary_component", {
"salary_component": "Basic Salary"
})
overtime_type.save()
return overtime_type

View File

@@ -572,7 +572,6 @@ class SalarySlip(TransactionBase):
if "applicable_amount" not in overtime_types_details[detail.overtime_type].keys(): if "applicable_amount" not in overtime_types_details[detail.overtime_type].keys():
component_amount = sum([data.default_amount for data in self.earnings if data.salary_component in \ component_amount = sum([data.default_amount for data in self.earnings if data.salary_component in \
overtime_types_details[detail.overtime_type]["components"] and not data.get('additional_salary', None)]) overtime_types_details[detail.overtime_type]["components"] and not data.get('additional_salary', None)])
overtime_types_details[detail.overtime_type]["applicable_daily_amount"] = component_amount/self.total_working_days overtime_types_details[detail.overtime_type]["applicable_daily_amount"] = component_amount/self.total_working_days
standard_working_hours = detail.standard_working_time/3600 standard_working_hours = detail.standard_working_time/3600

View File

@@ -23,6 +23,8 @@ class TestSalarySlip(unittest.TestCase):
def tearDown(self): def tearDown(self):
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0) frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
frappe.db.sql("DELETE FROM `tabOvertime Type`")
frappe.db.sql("DELETE FROM `tabOvertime Slip`")
frappe.set_user("Administrator") frappe.set_user("Administrator")
def test_payment_days_based_on_attendance(self): def test_payment_days_based_on_attendance(self):
@@ -460,6 +462,63 @@ class TestSalarySlip(unittest.TestCase):
# undelete fixture data # undelete fixture data
frappe.db.rollback() frappe.db.rollback()
def test_overtime_calculation(self):
from erpnext.payroll.doctype.overtime_type.test_overtime_type import create_overtime_type
from erpnext.payroll.doctype.overtime_slip.test_overtime_slip import create_overtime_slip
from erpnext.payroll.doctype.overtime_slip.test_overtime_slip import create_attendance_records_for_overtime
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
employee = make_employee("overtime_calc@salary.slip")
salary_structure = make_salary_structure("structure for Overtime 2", "Monthly", employee=employee)
frappe.db.set_value("Payroll Settings", None, "overtime_based_on", "Attendance")
frappe.db.set_value("Payroll Settings", None, "fetch_standard_working_hours_from_shift_type", 0)
self.get_salary_component_for_overtime()
frappe.db.set_value("Payroll Settings", None, "overtime_salary_component", "Overtime Allowance")
overtime_type = create_overtime_type(employee = employee).name
create_attendance_records_for_overtime(employee, overtime_type=overtime_type)
slip = create_overtime_slip(employee)
slip.status = "Approved"
slip.submit()
salary_slip = make_salary_slip(salary_structure.name, employee = employee)
overtime_component_details = {}
applicable_amount = 0
for earning in salary_slip.earnings:
if earning.salary_component == "Overtime Allowance":
overtime_component_details = earning
if earning.salary_component == "Basic Salary":
applicable_amount = earning.default_amount
self.assertIn("Overtime Allowance", overtime_component_details.salary_component)
self.assertEqual(slip.name, overtime_component_details.overtime_slips)
daily_wages = applicable_amount/ salary_slip.total_working_days
hourly_wages = daily_wages/ frappe.db.get_single_value("Hr Settings", "standard_working_hours")
overtime_amount = hourly_wages * 4 * 1.25
#since multiplier is defined as 1.25 and
self.assertEquals(flt(overtime_amount, 2), flt(overtime_component_details.amount, 2) )
# formula = sum(applicable_component)/(working_days)/ daily_standard_working_time * overtime hours * multiplier
def get_salary_component_for_overtime(self):
component = [{
"salary_component": 'Overtime Allowance',
"abbr":'OA',
"type": "Earning",
"amount_based_on_formula": 0
}]
company = erpnext.get_default_company()
make_salary_component(component, test_tax = 0, company_list=[company])
def make_activity_for_employee(self): def make_activity_for_employee(self):
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type") activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
activity_type.billing_rate = 50 activity_type.billing_rate = 50

View File

@@ -59,7 +59,7 @@ frappe.ui.form.on("Timesheet", {
} }
} }
if (frm.doc.docstatus < 1) { if (frm.doc.docstatus < 1 && !cur_frm.doc.__islocal) {
let button = 'Start Timer'; let button = 'Start Timer';
$.each(frm.doc.time_logs || [], function (i, row) { $.each(frm.doc.time_logs || [], function (i, row) {

View File

@@ -119,7 +119,8 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Employee", "label": "Employee",
"options": "Employee" "options": "Employee",
"reqd": 1
}, },
{ {
"depends_on": "employee", "depends_on": "employee",
@@ -341,7 +342,7 @@
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-06-14 17:10:31.434084", "modified": "2021-06-17 17:45:12.064677",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Timesheet", "name": "Timesheet",

View File

@@ -84,19 +84,17 @@ class Timesheet(Document):
frappe.bold("Overtime On")) frappe.bold("Overtime On"))
) )
maximum_overtime_hours_allowed = frappe.db.get_single_value("Payroll Settings", "maximum_overtime_hours_allowed") maximum_overtime_hours_allowed = frappe.db.get_single_value("Payroll Settings", "maximum_overtime_hours_allowed")
if int(maximum_overtime_hours_allowed) and data.overtime_hours > maximum_overtime_hours_allowed:
if data.overtime_hours <= maximum_overtime_hours_allowed:
total_overtime_hours += data.overtime_hours
else:
frappe.throw(_("Row {0}: Overtime Hours can not be greater than {1} for a day. You can change this in Payroll Settings"). frappe.throw(_("Row {0}: Overtime Hours can not be greater than {1} for a day. You can change this in Payroll Settings").
format( format(
str(data.idx), str(data.idx),
frappe.bold(str(maximum_overtime_hours_allowed)) frappe.bold(str(maximum_overtime_hours_allowed))
)) ))
else:
total_overtime_hours += data.overtime_hours
else: else:
frappe.throw(_('Please Set "Calculate Overtime Based On" to TimeSheet In Payroll Settings')) frappe.throw(_('Please Set "Calculate Overtime Based On" to TimeSheet In Payroll Settings'))
if total_overtime_hours: if total_overtime_hours:
self.total_overtime_hours = total_overtime_hours self.total_overtime_hours = total_overtime_hours
self.overtime_type =overtime_type self.overtime_type =overtime_type
@@ -140,20 +138,8 @@ class Timesheet(Document):
self.update_task_and_project() self.update_task_and_project()
def on_submit(self): def on_submit(self):
self.validate_mandatory_fields()
self.update_task_and_project() self.update_task_and_project()
def validate_mandatory_fields(self):
for data in self.time_logs:
if not data.from_time and not data.to_time:
frappe.throw(_("Row {0}: From Time and To Time is mandatory.").format(data.idx))
if not data.activity_type and self.employee:
frappe.throw(_("Row {0}: Activity Type is mandatory.").format(data.idx))
if flt(data.hours) == 0.0:
frappe.throw(_("Row {0}: Hours value must be greater than zero.").format(data.idx))
def update_task_and_project(self): def update_task_and_project(self):
tasks, projects = [], [] tasks, projects = [], []

View File

@@ -53,14 +53,16 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Activity Type", "label": "Activity Type",
"options": "Activity Type" "options": "Activity Type",
"reqd": 1
}, },
{ {
"columns": 2, "columns": 2,
"fieldname": "from_time", "fieldname": "from_time",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"in_list_view": 1, "in_list_view": 1,
"label": "From Time" "label": "From Time",
"reqd": 1
}, },
{ {
"fieldname": "section_break_3", "fieldname": "section_break_3",
@@ -76,12 +78,14 @@
"fieldname": "hours", "fieldname": "hours",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Working Hours" "label": "Working Hours",
"reqd": 1
}, },
{ {
"fieldname": "to_time", "fieldname": "to_time",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "To Time" "label": "To Time",
"reqd": 1
}, },
{ {
"default": "0", "default": "0",
@@ -308,7 +312,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-06-10 15:17:20.846091", "modified": "2021-06-17 17:44:09.306304",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Timesheet Detail", "name": "Timesheet Detail",