diff --git a/hrms/hr/report/employee_leave_balance/employee_leave_balance.py b/hrms/hr/report/employee_leave_balance/employee_leave_balance.py index baebd62419..169934604f 100644 --- a/hrms/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/hrms/hr/report/employee_leave_balance/employee_leave_balance.py @@ -58,7 +58,7 @@ def get_columns() -> list[dict]: "width": 150, }, { - "label": _("New Leave(s) Allocated"), + "label": _("Leave(s) Allocated"), "fieldtype": "float", "fieldname": "leaves_allocated", "width": 200, @@ -116,14 +116,22 @@ def get_data(filters: Filters) -> list: new_allocation, expired_leaves, carry_forwarded_leaves = get_allocated_and_expired_leaves( filters.from_date, filters.to_date, employee.name, leave_type ) - opening = get_opening_balance(employee.name, leave_type, filters, carry_forwarded_leaves) + on_allocation_boundary = is_opening_balance_on_allocation_boundary( + employee.name, leave_type, filters + ) + opening = get_opening_balance( + employee.name, leave_type, filters, carry_forwarded_leaves, on_allocation_boundary + ) + allocated_leaves = new_allocation + carry_forwarded_leaves + if on_allocation_boundary: + allocated_leaves -= carry_forwarded_leaves - row.leaves_allocated = flt(new_allocation, precision) + row.leaves_allocated = flt(allocated_leaves, precision) row.leaves_expired = flt(expired_leaves, precision) row.opening_balance = flt(opening, precision) row.leaves_taken = flt(leaves_taken, precision) - closing = new_allocation + opening - (row.leaves_expired + leaves_taken) + closing = allocated_leaves + opening - (row.leaves_expired + leaves_taken) row.closing_balance = flt(closing, precision) row.indent = 1 data.append(row) @@ -158,19 +166,17 @@ def get_employees(filters: Filters) -> list[dict]: def get_opening_balance( - employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float + employee: str, + leave_type: str, + filters: Filters, + carry_forwarded_leaves: float, + on_allocation_boundary: bool, ) -> float: # allocation boundary condition # opening balance is the closing leave balance 1 day before the filter start date opening_balance_date = add_days(filters.from_date, -1) - allocation = get_previous_allocation(filters.from_date, leave_type, employee) - if ( - allocation - and allocation.get("to_date") - and opening_balance_date - and getdate(allocation.get("to_date")) == getdate(opening_balance_date) - ): + if on_allocation_boundary: # if opening balance date is same as the previous allocation's expiry # then opening balance should only consider carry forwarded leaves opening_balance = carry_forwarded_leaves @@ -181,6 +187,18 @@ def get_opening_balance( return opening_balance +def is_opening_balance_on_allocation_boundary(employee: str, leave_type: str, filters: Filters) -> bool: + opening_balance_date = add_days(filters.from_date, -1) + allocation = get_previous_allocation(filters.from_date, leave_type, employee) + + return bool( + allocation + and allocation.get("to_date") + and opening_balance_date + and getdate(allocation.get("to_date")) == getdate(opening_balance_date) + ) + + def get_allocated_and_expired_leaves( from_date: str, to_date: str, employee: str, leave_type: str ) -> tuple[float, float, float]: @@ -205,7 +223,7 @@ def get_allocated_leaves(from_date, to_date, employee, leave_type): & (ledger.transaction_type.isin(["Leave Allocation", "Leave Adjustment"])) & (ledger.employee == employee) & (ledger.leave_type == leave_type) - & ((ledger.from_date[from_date:to_date]) | (ledger.to_date[from_date:to_date])) + & (ledger.from_date[from_date:to_date]) & ((ledger.is_expired == 0) & (ledger.is_carry_forward == 0)) ) ).run()[0][0] @@ -239,7 +257,7 @@ def get_cf_leaves(from_date, to_date, employee, leave_type): & (ledger.transaction_type == "Leave Allocation") & (ledger.employee == employee) & (ledger.leave_type == leave_type) - & ((ledger.from_date[from_date:to_date]) | (ledger.to_date[from_date:to_date])) + & (ledger.from_date[from_date:to_date]) & ((ledger.is_expired == 0) & (ledger.is_carry_forward == 1)) ) ).run()[0][0] diff --git a/hrms/hr/report/employee_leave_balance/test_employee_leave_balance.py b/hrms/hr/report/employee_leave_balance/test_employee_leave_balance.py index 7e57f6189f..fa2bf77616 100644 --- a/hrms/hr/report/employee_leave_balance/test_employee_leave_balance.py +++ b/hrms/hr/report/employee_leave_balance/test_employee_leave_balance.py @@ -205,6 +205,58 @@ def test_opening_balance_considers_carry_forwarded_leaves(self): ) self.assertEqual(report[1][0].opening_balance, opening_balance) + def test_carry_forwarded_leaves_are_included_when_they_expire(self): + leave_type = create_leave_type( + leave_type_name="_Test_CF_report_expiry", + is_carry_forward=1, + expire_carry_forwarded_leaves_after_days=7, + ) + + previous_allocation = make_allocation_record( + employee=self.employee_id, + from_date=add_days(self.date, -45), + to_date=add_days(self.date, -31), + leave_type=leave_type.name, + leaves=7, + ) + current_allocation = make_allocation_record( + employee=self.employee_id, + from_date=add_days(self.date, -20), + to_date=add_days(self.date, 30), + carry_forward=True, + leave_type=leave_type.name, + leaves=11, + ) + + leave_application = make_leave_application( + self.employee_id, + add_days(current_allocation.from_date, 8), + add_days(current_allocation.from_date, 16), + leave_type.name, + ) + leave_application.reload() + process_expired_allocation() + + filters = frappe._dict( + { + "from_date": current_allocation.from_date, + "to_date": current_allocation.to_date, + "employee": self.employee_id, + } + ) + report = execute(filters) + row = next(row for row in report[1] if row.leave_type == leave_type.name) + + expected_allocated = current_allocation.new_leaves_allocated + current_allocation.unused_leaves + expected_closing = row.opening_balance + row.leaves_allocated - row.leaves_expired - row.leaves_taken + + self.assertEqual(current_allocation.unused_leaves, previous_allocation.new_leaves_allocated) + self.assertEqual(row.opening_balance, 0) + self.assertEqual(row.leaves_allocated, flt(expected_allocated)) + self.assertEqual(row.leaves_expired, flt(current_allocation.unused_leaves)) + self.assertEqual(row.leaves_taken, flt(leave_application.total_leave_days)) + self.assertEqual(row.closing_balance, flt(expected_closing)) + @assign_holiday_list("_Test Emp Balance Holiday List", "_Test Company") def test_employee_status_filter(self): frappe.get_doc(test_records[0]).insert() diff --git a/hrms/locale/de.po b/hrms/locale/de.po index 64055ba674..aa423a39e7 100644 --- a/hrms/locale/de.po +++ b/hrms/locale/de.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: contact@frappe.io\n" "POT-Creation-Date: 2026-06-21 10:42+0000\n" -"PO-Revision-Date: 2026-06-22 20:40\n" +"PO-Revision-Date: 2026-06-23 20:42\n" "Last-Translator: contact@frappe.io\n" "Language-Team: German\n" "MIME-Version: 1.0\n" @@ -250,7 +250,26 @@ msgid "
Condition: employment_type==\"Intern\"\n"
"Amount: 1000\n"
""
-msgstr ""
+msgstr "Hinweise:
\n\n" +"base, um das Grundgehalt des Mitarbeiters zu nutzenBS = GrundgehaltEmployment Type = employment_typeBranch = branchPayment Days = payment_daysLeave without pay = leave_without_paybase\n"
+"Bedingung: base < 10000\n"
+"Formel: base * .2BS \n"
+"Bedingung: BS > 2000\n"
+"Formel: BS * .1employment_type \n"
+"Bedingung: employment_type==\"Intern\"\n"
+"Betrag: 1000