Skip to content

Commit b2d3014

Browse files
authored
Merge pull request #380 from frappe/version-14-hotfix
2 parents 2e1c6ec + 8b80059 commit b2d3014

8 files changed

Lines changed: 238 additions & 47 deletions

File tree

hrms/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "14.1.0"
1+
__version__ = "14.1.1"

hrms/hr/doctype/leave_application/leave_application.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ def get_leave_allocation_records(employee, date, leave_type=None):
864864
Min(Ledger.from_date).as_("from_date"),
865865
Max(Ledger.to_date).as_("to_date"),
866866
Ledger.leave_type,
867+
Ledger.employee,
867868
)
868869
.where(
869870
(Ledger.from_date <= date)
@@ -903,6 +904,7 @@ def get_leave_allocation_records(employee, date, leave_type=None):
903904
"unused_leaves": d.cf_leaves,
904905
"new_leaves_allocated": d.new_leaves,
905906
"leave_type": d.leave_type,
907+
"employee": d.employee,
906908
}
907909
),
908910
)
@@ -941,26 +943,51 @@ def _get_remaining_leaves(remaining_leaves, end_date):
941943

942944
return remaining_leaves
943945

944-
leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(
945-
leaves_taken
946-
)
947-
948-
# balance for carry forwarded leaves
949946
if cf_expiry and allocation.unused_leaves:
947+
# allocation contains both carry forwarded and new leaves
948+
new_leaves_taken, cf_leaves_taken = get_new_and_cf_leaves_taken(allocation, cf_expiry)
949+
950950
if getdate(date) > getdate(cf_expiry):
951-
# carry forwarded leave expiry date passed
951+
# carry forwarded leaves have expired
952952
cf_leaves = remaining_cf_leaves = 0
953953
else:
954-
cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken)
954+
cf_leaves = flt(allocation.unused_leaves) + flt(cf_leaves_taken)
955955
remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry)
956956

957-
leave_balance = flt(allocation.new_leaves_allocated) + flt(cf_leaves)
958-
leave_balance_for_consumption = flt(allocation.new_leaves_allocated) + flt(remaining_cf_leaves)
957+
# new leaves allocated - new leaves taken + cf leave balance
958+
# Note: `new_leaves_taken` is added here because its already a -ve number in the ledger
959+
leave_balance = (flt(allocation.new_leaves_allocated) + flt(new_leaves_taken)) + flt(cf_leaves)
960+
leave_balance_for_consumption = (
961+
flt(allocation.new_leaves_allocated) + flt(new_leaves_taken)
962+
) + flt(remaining_cf_leaves)
963+
else:
964+
# allocation only contains newly allocated leaves
965+
leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(
966+
leaves_taken
967+
)
959968

960969
remaining_leaves = _get_remaining_leaves(leave_balance_for_consumption, allocation.to_date)
961970
return frappe._dict(leave_balance=leave_balance, leave_balance_for_consumption=remaining_leaves)
962971

963972

973+
def get_new_and_cf_leaves_taken(allocation: Dict, cf_expiry: str) -> Tuple[float, float]:
974+
"""returns new leaves taken and carry forwarded leaves taken within an allocation period based on cf leave expiry"""
975+
cf_leaves_taken = get_leaves_for_period(
976+
allocation.employee, allocation.leave_type, allocation.from_date, cf_expiry
977+
)
978+
new_leaves_taken = get_leaves_for_period(
979+
allocation.employee, allocation.leave_type, add_days(cf_expiry, 1), allocation.to_date
980+
)
981+
982+
# using abs because leaves taken is a -ve number in the ledger
983+
if abs(cf_leaves_taken) > allocation.unused_leaves:
984+
# adjust the excess leaves in new_leaves_taken
985+
new_leaves_taken += -(abs(cf_leaves_taken) - allocation.unused_leaves)
986+
cf_leaves_taken = -allocation.unused_leaves
987+
988+
return new_leaves_taken, cf_leaves_taken
989+
990+
964991
def get_leaves_for_period(
965992
employee: str,
966993
leave_type: str,

hrms/hr/doctype/leave_application/test_leave_application.py

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
get_leave_allocation_records,
3030
get_leave_balance_on,
3131
get_leave_details,
32+
get_new_and_cf_leaves_taken,
3233
)
3334
from hrms.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
3435
create_assignment_for_multiple_employees,
@@ -97,6 +98,9 @@ def setUp(self):
9798
from_date = get_year_start(getdate())
9899
to_date = get_year_ending(getdate())
99100
self.holiday_list = make_holiday_list(from_date=from_date, to_date=to_date)
101+
list_without_weekly_offs = make_holiday_list(
102+
"Holiday List w/o Weekly Offs", from_date=from_date, to_date=to_date, add_weekly_offs=False
103+
)
100104

101105
if not frappe.db.exists("Leave Type", "_Test Leave Type"):
102106
frappe.get_doc(
@@ -922,8 +926,12 @@ def test_get_leave_details_for_dashboard(self):
922926
self.assertEqual(leave_allocation["leaves_pending_approval"], 1)
923927
self.assertEqual(leave_allocation["remaining_leaves"], 26)
924928

925-
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
929+
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
926930
def test_leave_details_with_expired_cf_leaves(self):
931+
"""Tests leave details:
932+
Case 1: All leaves available before cf leave expiry
933+
Case 2: Remaining Leaves after cf leave expiry
934+
"""
927935
employee = get_employee()
928936
leave_type = create_leave_type(
929937
leave_type_name="_Test_CF_leave_expiry",
@@ -936,11 +944,11 @@ def test_leave_details_with_expired_cf_leaves(self):
936944
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
937945
)
938946

939-
# all leaves available before cf leave expiry
947+
# case 1: all leaves available before cf leave expiry
940948
leave_details = get_leave_details(employee.name, add_days(cf_expiry, -1))
941949
self.assertEqual(leave_details["leave_allocation"][leave_type.name]["remaining_leaves"], 30.0)
942950

943-
# cf leaves expired
951+
# case 2: cf leaves expired
944952
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 1))
945953
expected_data = {
946954
"total_leaves": 30.0,
@@ -949,6 +957,119 @@ def test_leave_details_with_expired_cf_leaves(self):
949957
"leaves_pending_approval": 0.0,
950958
"remaining_leaves": 15.0,
951959
}
960+
961+
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
962+
963+
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
964+
def test_leave_details_with_application_across_cf_expiry(self):
965+
"""Tests leave details with leave application across cf expiry, such that:
966+
cf leaves are partially expired and partially consumed
967+
"""
968+
employee = get_employee()
969+
leave_type = create_leave_type(
970+
leave_type_name="_Test_CF_leave_expiry",
971+
is_carry_forward=1,
972+
expire_carry_forwarded_leaves_after_days=90,
973+
).insert()
974+
975+
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
976+
cf_expiry = frappe.db.get_value(
977+
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
978+
)
979+
980+
# leave application across cf expiry
981+
application = make_leave_application(
982+
employee.name,
983+
cf_expiry,
984+
add_days(cf_expiry, 3),
985+
leave_type.name,
986+
)
987+
988+
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 4))
989+
expected_data = {
990+
"total_leaves": 30.0,
991+
"expired_leaves": 14.0,
992+
"leaves_taken": 4.0,
993+
"leaves_pending_approval": 0.0,
994+
"remaining_leaves": 12.0,
995+
}
996+
997+
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
998+
999+
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
1000+
def test_leave_details_with_application_across_cf_expiry_2(self):
1001+
"""Tests the same case as above but with leave days greater than cf leaves allocated"""
1002+
employee = get_employee()
1003+
leave_type = create_leave_type(
1004+
leave_type_name="_Test_CF_leave_expiry",
1005+
is_carry_forward=1,
1006+
expire_carry_forwarded_leaves_after_days=90,
1007+
).insert()
1008+
1009+
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
1010+
cf_expiry = frappe.db.get_value(
1011+
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
1012+
)
1013+
1014+
# leave application across cf expiry, 20 days leave
1015+
application = make_leave_application(
1016+
employee.name,
1017+
add_days(cf_expiry, -16),
1018+
add_days(cf_expiry, 3),
1019+
leave_type.name,
1020+
)
1021+
1022+
# 15 cf leaves and 5 new leaves should be consumed
1023+
# after adjustment of the actual days breakup (17 and 3) because only 15 cf leaves have been allocated
1024+
new_leaves_taken, cf_leaves_taken = get_new_and_cf_leaves_taken(leave_alloc, cf_expiry)
1025+
self.assertEqual(new_leaves_taken, -5.0)
1026+
self.assertEqual(cf_leaves_taken, -15.0)
1027+
1028+
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 4))
1029+
expected_data = {
1030+
"total_leaves": 30.0,
1031+
"expired_leaves": 0,
1032+
"leaves_taken": 20.0,
1033+
"leaves_pending_approval": 0.0,
1034+
"remaining_leaves": 10.0,
1035+
}
1036+
1037+
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
1038+
1039+
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
1040+
def test_leave_details_with_application_after_cf_expiry(self):
1041+
"""Tests leave details with leave application after cf expiry, such that:
1042+
cf leaves are completely expired and only newly allocated leaves are consumed
1043+
"""
1044+
employee = get_employee()
1045+
leave_type = create_leave_type(
1046+
leave_type_name="_Test_CF_leave_expiry",
1047+
is_carry_forward=1,
1048+
expire_carry_forwarded_leaves_after_days=90,
1049+
).insert()
1050+
1051+
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
1052+
cf_expiry = frappe.db.get_value(
1053+
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
1054+
)
1055+
1056+
# leave application after cf expiry
1057+
application = make_leave_application(
1058+
employee.name,
1059+
add_days(cf_expiry, 1),
1060+
add_days(cf_expiry, 4),
1061+
leave_type.name,
1062+
)
1063+
1064+
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 4))
1065+
expected_data = {
1066+
"total_leaves": 30.0,
1067+
"expired_leaves": 15.0,
1068+
"leaves_taken": 4.0,
1069+
"leaves_pending_approval": 0.0,
1070+
"remaining_leaves": 11.0,
1071+
}
1072+
9521073
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
9531074

9541075
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
@@ -975,6 +1096,7 @@ def test_get_leave_allocation_records(self):
9751096
"unused_leaves": 15.0,
9761097
"new_leaves_allocated": 15.0,
9771098
"leave_type": leave_type.name,
1099+
"employee": employee.name,
9781100
}
9791101
self.assertEqual(details.get(leave_type.name), expected_data)
9801102

hrms/hr/doctype/shift_type/shift_type.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,18 @@ def get_attendance(self, logs):
9595
):
9696
early_exit = True
9797

98-
if (
99-
self.working_hours_threshold_for_half_day
100-
and total_working_hours < self.working_hours_threshold_for_half_day
101-
):
102-
return "Half Day", total_working_hours, late_entry, early_exit, in_time, out_time
10398
if (
10499
self.working_hours_threshold_for_absent
105100
and total_working_hours < self.working_hours_threshold_for_absent
106101
):
107102
return "Absent", total_working_hours, late_entry, early_exit, in_time, out_time
103+
104+
if (
105+
self.working_hours_threshold_for_half_day
106+
and total_working_hours < self.working_hours_threshold_for_half_day
107+
):
108+
return "Half Day", total_working_hours, late_entry, early_exit, in_time, out_time
109+
108110
return "Present", total_working_hours, late_entry, early_exit, in_time, out_time
109111

110112
def mark_absent_for_dates_with_no_attendance(self, employee):

hrms/hr/doctype/shift_type/test_shift_type.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ def test_working_hours_threshold_for_absent_and_half_day_1(self):
141141
employee = make_employee("test_employee_checkin@example.com", company="_Test Company")
142142
shift_type = setup_shift_type(
143143
shift_type="Half Day + Absent Test",
144-
working_hours_threshold_for_half_day=1,
145-
working_hours_threshold_for_absent=2,
144+
working_hours_threshold_for_half_day=2,
145+
working_hours_threshold_for_absent=1,
146146
)
147147
date = getdate()
148148
make_shift_assignment(shift_type.name, employee, date)
@@ -151,7 +151,7 @@ def test_working_hours_threshold_for_absent_and_half_day_1(self):
151151
log_in = make_checkin(employee, timestamp)
152152
self.assertEqual(log_in.shift, shift_type.name)
153153

154-
timestamp = datetime.combine(date, get_time("08:45:00"))
154+
timestamp = datetime.combine(date, get_time("09:30:00"))
155155
log_out = make_checkin(employee, timestamp)
156156
self.assertEqual(log_out.shift, shift_type.name)
157157

@@ -161,7 +161,7 @@ def test_working_hours_threshold_for_absent_and_half_day_1(self):
161161
"Attendance", {"shift": shift_type.name}, ["status", "working_hours"], as_dict=True
162162
)
163163
self.assertEqual(attendance.status, "Half Day")
164-
self.assertEqual(attendance.working_hours, 0.75)
164+
self.assertEqual(attendance.working_hours, 1.5)
165165

166166
def test_working_hours_threshold_for_absent_and_half_day_2(self):
167167
# considers absent over half day
@@ -170,8 +170,8 @@ def test_working_hours_threshold_for_absent_and_half_day_2(self):
170170
employee = make_employee("test_employee_checkin@example.com", company="_Test Company")
171171
shift_type = setup_shift_type(
172172
shift_type="Half Day + Absent Test",
173-
working_hours_threshold_for_half_day=1,
174-
working_hours_threshold_for_absent=2,
173+
working_hours_threshold_for_half_day=2,
174+
working_hours_threshold_for_absent=1,
175175
)
176176
date = getdate()
177177
make_shift_assignment(shift_type.name, employee, date)
@@ -180,7 +180,7 @@ def test_working_hours_threshold_for_absent_and_half_day_2(self):
180180
log_in = make_checkin(employee, timestamp)
181181
self.assertEqual(log_in.shift, shift_type.name)
182182

183-
timestamp = datetime.combine(date, get_time("09:30:00"))
183+
timestamp = datetime.combine(date, get_time("08:45:00"))
184184
log_out = make_checkin(employee, timestamp)
185185
self.assertEqual(log_out.shift, shift_type.name)
186186

hrms/payroll/doctype/payroll_entry/payroll_entry.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,6 @@ def create_journal_entry(self, je_payment_amount, user_remark):
690690

691691
if self.employee_based_payroll_payable_entries:
692692
for employee, employee_details in self.employee_based_payroll_payable_entries.items():
693-
je_payment_amount = employee_details["earnings"] - employee_details["deduction"]
694693
je_payment_amount = employee_details.get("earnings") - (
695694
employee_details.get("deductions") or 0
696695
)

0 commit comments

Comments
 (0)