From 73b3121127598b241e8ceb4dfd3573ab080ccda7 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 18 Jun 2021 15:43:12 +0530 Subject: [PATCH] fix: some enhancemets and test cases --- erpnext/hr/doctype/attendance/attendance.py | 22 +-- .../employee_checkin/employee_checkin.py | 3 - .../employee_checkin/test_employee_checkin.py | 8 +- erpnext/hr/doctype/shift_type/shift_type.py | 2 +- .../hr/doctype/shift_type/test_shift_type.py | 10 ++ .../overtime_salary_component.json | 2 +- .../doctype/overtime_slip/overtime_slip.json | 27 +++- .../doctype/overtime_slip/overtime_slip.py | 24 +-- .../overtime_slip/test_overtime_slip.py | 152 +++++++++++++++++- .../overtime_type/test_overtime_type.py | 36 ++++- .../doctype/salary_slip/salary_slip.py | 1 - .../doctype/salary_slip/test_salary_slip.py | 59 +++++++ .../projects/doctype/timesheet/timesheet.js | 2 +- .../projects/doctype/timesheet/timesheet.json | 5 +- .../projects/doctype/timesheet/timesheet.py | 20 +-- .../timesheet_detail/timesheet_detail.json | 14 +- 16 files changed, 323 insertions(+), 64 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index a5a18be302e..7ffe96417a6 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -135,26 +135,26 @@ def get_shift_type(employee, attendance_date): @frappe.whitelist() def get_overtime_type(employee): + overtime_type = None emp_department = frappe.db.get_value("Employee", employee, "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']) - if len(overtime_type): - overtime_type = overtime_type[0].name - + if len(overtime_type_doc): + overtime_type = overtime_type_doc[0].name emp_grade = frappe.db.get_value("Employee", employee, "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}, 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 = frappe.get_list("Overtime Type", filters={ + overtime_type_doc = frappe.get_list("Overtime Type", filters={ "applicable_for": "Employee", "employee": employee}, fields=['name']) - if len(overtime_type): - overtime_type = overtime_type[0].name + if len(overtime_type_doc): + overtime_type = overtime_type_doc[0].name return overtime_type diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index b2153b79db3..fbcc191887a 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -102,7 +102,6 @@ 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) @@ -153,8 +152,6 @@ 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: diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index 9f12ef24e62..a1d52a22723 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -42,11 +42,11 @@ class TestEmployeeCheckin(unittest.TestCase): self.assertEqual(logs_count, 4) attendance_count = frappe.db.count('Attendance', {'status':'Present', 'working_hours':8.2, 'employee':employee, 'attendance_date':now_date}) - self.assertEqual(attendance_count, 1) + self.assertEqual(attendance_count, 1) def test_calculate_working_hours(self): 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', 'Every Valid Check-in and Check-out'] logs_type_1 = [ @@ -88,12 +88,12 @@ def make_n_checkins(employee, n, hours_to_reverse=1): return logs -def make_checkin(employee, time=now_datetime()): +def make_checkin(employee, time=now_datetime(), log_type = "IN"): log = frappe.get_doc({ "doctype": "Employee Checkin", "employee" : employee, "time" : time, "device_id" : "device1", - "log_type" : "IN" + "log_type" : log_type }).insert() return log diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index e6d3dd7db9c..39a1ef8a9da 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -24,7 +24,7 @@ class ShiftType(Document): 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])) + 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 diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.py b/erpnext/hr/doctype/shift_type/test_shift_type.py index bc4f0eafcd5..40ed4e4a466 100644 --- a/erpnext/hr/doctype/shift_type/test_shift_type.py +++ b/erpnext/hr/doctype/shift_type/test_shift_type.py @@ -8,3 +8,13 @@ import unittest class TestShiftType(unittest.TestCase): 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 diff --git a/erpnext/payroll/doctype/overtime_salary_component/overtime_salary_component.json b/erpnext/payroll/doctype/overtime_salary_component/overtime_salary_component.json index 3276bb3f5c7..02def575477 100644 --- a/erpnext/payroll/doctype/overtime_salary_component/overtime_salary_component.json +++ b/erpnext/payroll/doctype/overtime_salary_component/overtime_salary_component.json @@ -20,7 +20,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-25 12:49:03.287694", + "modified": "2021-06-16 14:48:59.476787", "modified_by": "Administrator", "module": "Payroll", "name": "Overtime Salary Component", diff --git a/erpnext/payroll/doctype/overtime_slip/overtime_slip.json b/erpnext/payroll/doctype/overtime_slip/overtime_slip.json index aef230c344d..fcbfdc96f5f 100644 --- a/erpnext/payroll/doctype/overtime_slip/overtime_slip.json +++ b/erpnext/payroll/doctype/overtime_slip/overtime_slip.json @@ -148,7 +148,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-15 12:53:17.355755", + "modified": "2021-06-16 14:55:13.441709", "modified_by": "Administrator", "module": "Payroll", "name": "Overtime Slip", @@ -162,9 +162,32 @@ "print": 1, "read": 1, "report": 1, - "role": "System Manager", + "role": "HR Manager", "share": 1, + "submit": 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", diff --git a/erpnext/payroll/doctype/overtime_slip/overtime_slip.py b/erpnext/payroll/doctype/overtime_slip/overtime_slip.py index 1ced834df9c..7176c5d3643 100644 --- a/erpnext/payroll/doctype/overtime_slip/overtime_slip.py +++ b/erpnext/payroll/doctype/overtime_slip/overtime_slip.py @@ -9,14 +9,18 @@ from erpnext.payroll.doctype.gratuity.gratuity import get_salary_structure from frappe.model.document import Document class OvertimeSlip(Document): 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() if self.from_date >= self.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): self.get_emp_and_overtime_details() @@ -29,7 +33,8 @@ class OvertimeSlip(Document): "docstatus": ("<", 2), "employee": self.employee, "to_date": (">=", self.from_date), - "from_date": ("<=", self.to_date) + "from_date": ("<=", self.to_date), + "name": ("!=", self.name) }) if len(overtime_slips): form_link = get_link_to_form("Overtime Slip", overtime_slips[0].name) @@ -119,17 +124,13 @@ class OvertimeSlip(Document): AND ( 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 [] def get_timesheet_record(self): 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 FROM `TabTimesheet` AS ts INNER JOIN `tabTimesheet Detail` As tsd ON tsd.parent = ts.name @@ -141,7 +142,7 @@ class OvertimeSlip(Document): AND ( 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 [] @@ -156,7 +157,6 @@ def get_standard_working_hours(employee, date): standard_working_time = 0 - 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: diff --git a/erpnext/payroll/doctype/overtime_slip/test_overtime_slip.py b/erpnext/payroll/doctype/overtime_slip/test_overtime_slip.py index 915c56e0514..e208f1e0280 100644 --- a/erpnext/payroll/doctype/overtime_slip/test_overtime_slip.py +++ b/erpnext/payroll/doctype/overtime_slip/test_overtime_slip.py @@ -1,8 +1,154 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # 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 + 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 + diff --git a/erpnext/payroll/doctype/overtime_type/test_overtime_type.py b/erpnext/payroll/doctype/overtime_type/test_overtime_type.py index ba5f62fc6dc..1c85b21f679 100644 --- a/erpnext/payroll/doctype/overtime_type/test_overtime_type.py +++ b/erpnext/payroll/doctype/overtime_type/test_overtime_type.py @@ -1,8 +1,42 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import frappe import unittest class TestOvertimeType(unittest.TestCase): 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 + + + diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 32aaff2a04d..3a444974d96 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -572,7 +572,6 @@ class SalarySlip(TransactionBase): 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 \ 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 standard_working_hours = detail.standard_working_time/3600 diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 6e8d3b3f306..8f4db4901df 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -23,6 +23,8 @@ class TestSalarySlip(unittest.TestCase): def tearDown(self): 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") def test_payment_days_based_on_attendance(self): @@ -460,6 +462,63 @@ class TestSalarySlip(unittest.TestCase): # undelete fixture data 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): activity_type = frappe.get_doc("Activity Type", "_Test Activity Type") activity_type.billing_rate = 50 diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 74984740cbf..8f261d29f76 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -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'; $.each(frm.doc.time_logs || [], function (i, row) { diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index 23de2f07fab..4045e0f87d2 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -119,7 +119,8 @@ "fieldtype": "Link", "in_standard_filter": 1, "label": "Employee", - "options": "Employee" + "options": "Employee", + "reqd": 1 }, { "depends_on": "employee", @@ -341,7 +342,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-14 17:10:31.434084", + "modified": "2021-06-17 17:45:12.064677", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet", diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 38dab84b0e9..1f24939986c 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -84,19 +84,17 @@ class Timesheet(Document): frappe.bold("Overtime On")) ) maximum_overtime_hours_allowed = frappe.db.get_single_value("Payroll Settings", "maximum_overtime_hours_allowed") - - if data.overtime_hours <= maximum_overtime_hours_allowed: - total_overtime_hours += data.overtime_hours - else: + if int(maximum_overtime_hours_allowed) and data.overtime_hours > maximum_overtime_hours_allowed: frappe.throw(_("Row {0}: Overtime Hours can not be greater than {1} for a day. You can change this in Payroll Settings"). format( str(data.idx), frappe.bold(str(maximum_overtime_hours_allowed)) )) + else: + total_overtime_hours += data.overtime_hours else: frappe.throw(_('Please Set "Calculate Overtime Based On" to TimeSheet In Payroll Settings')) - if total_overtime_hours: self.total_overtime_hours = total_overtime_hours self.overtime_type =overtime_type @@ -140,20 +138,8 @@ class Timesheet(Document): self.update_task_and_project() def on_submit(self): - self.validate_mandatory_fields() 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): tasks, projects = [], [] diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json index cdb8bbe8c39..b99341a9a51 100644 --- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json +++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json @@ -53,14 +53,16 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Activity Type", - "options": "Activity Type" + "options": "Activity Type", + "reqd": 1 }, { "columns": 2, "fieldname": "from_time", "fieldtype": "Datetime", "in_list_view": 1, - "label": "From Time" + "label": "From Time", + "reqd": 1 }, { "fieldname": "section_break_3", @@ -76,12 +78,14 @@ "fieldname": "hours", "fieldtype": "Float", "in_list_view": 1, - "label": "Working Hours" + "label": "Working Hours", + "reqd": 1 }, { "fieldname": "to_time", "fieldtype": "Datetime", - "label": "To Time" + "label": "To Time", + "reqd": 1 }, { "default": "0", @@ -308,7 +312,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-10 15:17:20.846091", + "modified": "2021-06-17 17:44:09.306304", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet Detail",