diff --git a/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index d59a93e73d..0f87255457 100644 --- a/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import DATE_FORMAT, flt, getdate, today +from frappe.utils import DATE_FORMAT, flt, get_link_to_form, getdate, today class LeaveLedgerEntry(Document): @@ -40,7 +40,11 @@ def validate_leave_allocation_against_leave_application(ledger): if leave_application_records: frappe.throw( _("Leave allocation {0} is linked with the Leave Application {1}").format( - ledger.transaction_name, ", ".join(leave_application_records) + ledger.transaction_name, + ", ".join( + get_link_to_form("Leave Application", application) + for application in leave_application_records + ), ) ) diff --git a/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js index 233f503187..75230a3670 100644 --- a/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js +++ b/hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js @@ -20,14 +20,22 @@ frappe.listview_settings['Leave Policy Assignment'] = { { fieldname: 'assignment_based_on', fieldtype: 'Select', - options: ["", "Leave Period"], + options: ["", "Leave Period", "Joining Date"], label: __('Assignment Based On'), onchange: () => { if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period") { + cur_dialog.set_df_property("effective_from", "reqd", 1); + cur_dialog.set_df_property("effective_from", "hidden", 0); cur_dialog.set_df_property("effective_from", "read_only", 1); cur_dialog.set_df_property("leave_period", "reqd", 1); cur_dialog.set_df_property("effective_to", "read_only", 1); + } else if (cur_dialog.fields_dict.assignment_based_on.value === "Joining Date") { + cur_dialog.set_df_property("effective_from", "reqd", 0); + cur_dialog.set_df_property("effective_from", "hidden", 1); + cur_dialog.set_value("effective_from", ""); } else { + cur_dialog.set_df_property("effective_from", "reqd", 1); + cur_dialog.set_df_property("effective_from", "hidden", 0); cur_dialog.set_df_property("effective_from", "read_only", 0); cur_dialog.set_df_property("leave_period", "reqd", 0); cur_dialog.set_df_property("effective_to", "read_only", 0); diff --git a/hrms/hr/doctype/shift_assignment/shift_assignment.py b/hrms/hr/doctype/shift_assignment/shift_assignment.py index 70f5d99043..0ec3ce37cc 100644 --- a/hrms/hr/doctype/shift_assignment/shift_assignment.py +++ b/hrms/hr/doctype/shift_assignment/shift_assignment.py @@ -171,11 +171,14 @@ def get_shift_assignments(start: str, end: str, filters: str | list | None = Non if not filters: filters = [] - filters.extend([["start_date", ">=", start], ["end_date", "<=", end], ["docstatus", "=", 1]]) + filters.extend([["start_date", "<=", end], ["docstatus", "=", 1]]) + + or_filters = [["end_date", ">=", start], ["end_date", "is", "not set"]] return frappe.get_list( "Shift Assignment", filters=filters, + or_filters=or_filters, fields=[ "name", "start_date", diff --git a/hrms/hr/doctype/shift_assignment/test_shift_assignment.py b/hrms/hr/doctype/shift_assignment/test_shift_assignment.py index 7752223fb3..fe45b45cff 100644 --- a/hrms/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/hrms/hr/doctype/shift_assignment/test_shift_assignment.py @@ -230,17 +230,32 @@ def test_multiple_shift_assignments_for_same_day(self): def test_calendar(self): employee1 = make_employee("test_shift_assignment1@example.com", company="_Test Company") employee2 = make_employee("test_shift_assignment2@example.com", company="_Test Company") + employee3 = make_employee("test_shift_assignment3@example.com", company="_Test Company") shift_type = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="12:00:00") date = getdate() - shift1 = make_shift_assignment(shift_type.name, employee1, date) - make_shift_assignment(shift_type.name, employee2, date) + shift1 = make_shift_assignment(shift_type.name, employee1, date) # 1 day + make_shift_assignment(shift_type.name, employee2, date) # excluded due to employee filter + make_shift_assignment( + shift_type.name, employee3, add_days(date, -3), add_days(date, -2) + ) # excluded + shift2 = make_shift_assignment(shift_type.name, employee3, add_days(date, -1), date) # 2 days + shift3 = make_shift_assignment( + shift_type.name, employee3, add_days(date, 1), add_days(date, 2) + ) # 2 days + shift4 = make_shift_assignment( + shift_type.name, employee3, add_days(date, 30), add_days(date, 30) + ) # 1 day + make_shift_assignment(shift_type.name, employee3, add_days(date, 31)) # excluded events = get_events( - start=date, end=date, filters=[["Shift Assignment", "employee", "=", employee1, False]] + start=date, + end=add_days(date, 30), + filters=[["Shift Assignment", "employee", "!=", employee2, False]], ) - self.assertEqual(len(events), 1) - self.assertEqual(events[0]["name"], shift1.name) + self.assertEqual(len(events), 6) + for shift in events: + self.assertIn(shift["name"], [shift1.name, shift2.name, shift3.name, shift4.name]) def test_calendar_for_night_shift(self): employee1 = make_employee("test_shift_assignment1@example.com", company="_Test Company") diff --git a/hrms/hr/page/organizational_chart/organizational_chart.py b/hrms/hr/page/organizational_chart/organizational_chart.py index b8bfce5dc4..8be4802104 100644 --- a/hrms/hr/page/organizational_chart/organizational_chart.py +++ b/hrms/hr/page/organizational_chart/organizational_chart.py @@ -43,7 +43,7 @@ def get_connections(employee: str, lft: int, rgt: int) -> int: query = ( frappe.qb.from_(Employee) .select(Count(Employee.name)) - .where((Employee.lft > lft) & (Employee.rgt < rgt)) + .where((Employee.lft > lft) & (Employee.rgt < rgt) & (Employee.status == "Active")) ).run() return query[0][0] diff --git a/hrms/payroll/doctype/payroll_entry/payroll_entry.py b/hrms/payroll/doctype/payroll_entry/payroll_entry.py index 158b7ec261..c099da0ae3 100644 --- a/hrms/payroll/doctype/payroll_entry/payroll_entry.py +++ b/hrms/payroll/doctype/payroll_entry/payroll_entry.py @@ -1367,7 +1367,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): ] filters.pop("start_date") filters.pop("end_date") - if filters.get("salary_slip_based_on_timesheet"): + if "salary_slip_based_on_timesheet" in filters: filters.pop("salary_slip_based_on_timesheet") filters.pop("payroll_frequency") filters.pop("payroll_payable_account") diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.py b/hrms/payroll/doctype/salary_slip/salary_slip.py index ba2f85a39a..800e8902bd 100644 --- a/hrms/payroll/doctype/salary_slip/salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/salary_slip.py @@ -670,7 +670,7 @@ def calculate_lwp_ppl_and_absent_days_based_on_attendance(self, holidays, reliev not consider_marked_attendance_on_holidays and formatdate(d.attendance_date, "yyyy-mm-dd") in holidays ): - if d.status == "Absent" or ( + if d.status in ["Absent", "Half Day"] or ( d.leave_type and d.leave_type in leave_type_map.keys() and not leave_type_map[d.leave_type]["include_holiday"] diff --git a/hrms/payroll/doctype/salary_slip/test_salary_slip.py b/hrms/payroll/doctype/salary_slip/test_salary_slip.py index 8d2b7e78bd..5cd65e5b49 100644 --- a/hrms/payroll/doctype/salary_slip/test_salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/test_salary_slip.py @@ -556,6 +556,55 @@ def test_consider_marked_attendance_on_holidays_with_unmarked_attendance(self): # no_of_days - period before DOJ self.assertEqual(ss.payment_days, no_of_days[0] - 3 - 1) + @change_settings( + "Payroll Settings", + { + "payroll_based_on": "Attendance", + "consider_unmarked_attendance_as": "Present", + "include_holidays_in_total_working_days": 1, + "consider_marked_attendance_on_holidays": 0, + }, + ) + def test_consider_marked_attendance_on_holidays_with_half_day_on_holiday(self): + from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday + + no_of_days = get_no_of_days() + month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate()) + joining_date = add_days(month_start_date, 3) + + emp_id = make_employee( + "test_salary_slip_with_holidays_included1@salary.com", + status="Active", + date_of_joining=joining_date, + relieving_date=None, + ) + + for days in range(date_diff(month_end_date, joining_date) + 1): + date = add_days(joining_date, days) + if not is_holiday("Salary Slip Test Holiday List", date): + mark_attendance(emp_id, date, "Present", ignore_validate=True) + + # mark half day on holiday + first_sunday = get_first_sunday(for_date=joining_date, find_after_for_date=True) + mark_attendance(emp_id, first_sunday, "Half Day", ignore_validate=True) + + ss = make_employee_salary_slip( + emp_id, + "Monthly", + "Test Salary Slip With Holidays Included", + ) + + self.assertEqual(ss.total_working_days, no_of_days[0]) + # no_of_days - period before DOJ + self.assertEqual(ss.payment_days, no_of_days[0] - 3) + + # enable consider marked attendance on holidays + frappe.db.set_single_value("Payroll Settings", "consider_marked_attendance_on_holidays", 1) + ss.save() + self.assertEqual(ss.total_working_days, no_of_days[0]) + # no_of_days - period before DOJ - 0.5 LWP on holiday (half day present) + self.assertEqual(ss.payment_days, no_of_days[0] - 3 - 0.5) + @change_settings("Payroll Settings", {"include_holidays_in_total_working_days": 1}) def test_payment_days(self): from hrms.payroll.doctype.salary_structure.test_salary_structure import ( diff --git a/hrms/public/js/erpnext/journal_entry.js b/hrms/public/js/erpnext/journal_entry.js index b2e889f2f8..39984096ad 100644 --- a/hrms/public/js/erpnext/journal_entry.js +++ b/hrms/public/js/erpnext/journal_entry.js @@ -49,7 +49,7 @@ frappe.ui.form.on("Journal Entry", { ] }; - if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) { + if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) { out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]); // Filter by cost center if (jvd.cost_center) { @@ -61,7 +61,7 @@ frappe.ui.form.on("Journal Entry", { out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]); } - if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) { + if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) { // party_type and party mandatory frappe.model.validate_missing(jvd, "party_type"); frappe.model.validate_missing(jvd, "party");