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 "

Help

\n\n" "
Condition: employment_type==\"Intern\"
\n" "
Amount: 1000
\n" "" -msgstr "" +msgstr "

Hilfe

\n\n" +"

Hinweise:

\n\n" +"
    \n" +"
  1. Verwenden Sie das Feld base, um das Grundgehalt des Mitarbeiters zu nutzen
  2. \n" +"
  3. Verwenden Sie Abkürzungen der Gehaltskomponenten in Bedingungen und Formeln. BS = Grundgehalt
  4. \n" +"
  5. Verwenden Sie den Feldnamen für Mitarbeiterdetails in Bedingungen und Formeln. Employment Type = employment_typeBranch = branch
  6. \n" +"
  7. Verwenden Sie den Feldnamen aus dem Gehaltszettel in Bedingungen und Formeln. Payment Days = payment_daysLeave without pay = leave_without_pay
  8. \n" +"
  9. Ein direkter Betrag kann ebenfalls basierend auf einer Bedingung eingegeben werden. Siehe Beispiel 3
\n\n" +"

Beispiele

\n" +"
    \n" +"
  1. Berechnung des Grundgehalts basierend auf base\n" +"
    Bedingung: base < 10000
    \n" +"
    Formel: base * .2
  2. \n" +"
  3. Berechnung der HRA basierend auf dem Grundgehalt BS \n" +"
    Bedingung: BS > 2000
    \n" +"
    Formel: BS * .1
  4. \n" +"
  5. Berechnung der TDS basierend auf Beschäftigungsart employment_type \n" +"
    Bedingung: employment_type==\"Intern\"
    \n" +"
    Betrag: 1000
  6. \n" +"
" #. Content of the 'html_6' (HTML) field in DocType 'Taxable Salary Slab' #: hrms/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json @@ -3689,7 +3708,7 @@ msgstr "Fehler beim Aktualisieren von {0}" #: hrms/payroll/utils.py:134 msgid "Error while evaluating the {doctype} {doclink} at row {row_id}.

Error: {error}

Hint: {description}" -msgstr "" +msgstr "Fehler beim Auswerten von {doctype} {doclink} in Zeile {row_id}.

Fehler: {error}

Hinweis: {description}" #. Label of the estimated_cost_per_position (Currency) field in DocType #. 'Staffing Plan Detail' @@ -4642,7 +4661,7 @@ msgstr "Gratifikation" #. Name of a DocType #: hrms/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json msgid "Gratuity Applicable Component" -msgstr "" +msgstr "Abfindungsrelevante Komponente" #. Label of the gratuity_rule (Link) field in DocType 'Gratuity' #. Name of a DocType @@ -4654,7 +4673,7 @@ msgstr "Gratifikationsregel" #. Name of a DocType #: hrms/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json msgid "Gratuity Rule Slab" -msgstr "" +msgstr "Stufe der Abfindungsregel" #. Label of a Card Break in the Tenure Workspace #: hrms/hr/workspace/tenure/tenure.json @@ -5109,7 +5128,7 @@ msgstr "Anreizbetrag" #: hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js:122 msgid "Include Company Descendants" -msgstr "" +msgstr "Tochtergesellschaften einbeziehen" #. Label of the include_holidays (Check) field in DocType 'Attendance Request' #: hrms/hr/doctype/attendance_request/attendance_request.json @@ -5873,7 +5892,7 @@ msgstr "Späte Einreise" #. field in DocType 'Shift Type' #: hrms/hr/doctype/shift_type/shift_type.json msgid "Late Entry & Early Exit Settings for Auto Attendance" -msgstr "" +msgstr "Einstellungen für Spätantritt & Frühabgang (Auto-Anwesenheit)" #: hrms/hr/report/shift_attendance/shift_attendance.py:88 msgid "Late Entry By" @@ -6088,7 +6107,7 @@ msgstr "Ledger-Eintrag verlassen" #: hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py:43 msgid "Leave Ledger Entry's To date needs to be after From date. Currently, From Date is {0} and To Date is {1}" -msgstr "" +msgstr "Das „Bis-Datum“ des Urlaubskonto-Eintrags muss nach dem „Von-Datum“ liegen. Aktuell ist das Von-Datum {0} und das Bis-Datum {1}" #. Label of the leave_period (Link) field in DocType 'Leave Allocation' #. Option for the 'Dates Based On' (Select) field in DocType 'Leave Control @@ -10351,7 +10370,7 @@ msgstr "Die Tage zwischen {0} und {1} sind keine gültigen Feiertage." #: hrms/setup.py:132 msgid "The first Approver in the list will be set as the default Approver." -msgstr "" +msgstr "Der erste Genehmiger in der Liste wird automatisch als Standard festgelegt." #: hrms/hr/doctype/leave_type/leave_type.py:84 msgid "The fraction of Daily Salary per Leave should be between 0 and 1" @@ -11738,7 +11757,7 @@ msgstr "maxmustermann@email.de" #. Label of the modify_half_day_status (Check) field in DocType 'Attendance' #: hrms/hr/doctype/attendance/attendance.json msgid "modify_half_day_status" -msgstr "" +msgstr "Halbtagsstatus ändern" #: hrms/hr/doctype/department_approver/department_approver.py:103 msgid "or for the Employee's Department: {0}"