diff --git a/hrms/api/roster.py b/hrms/api/roster.py
index bd80a2ad6e..04e222db6e 100644
--- a/hrms/api/roster.py
+++ b/hrms/api/roster.py
@@ -273,27 +273,26 @@ def get_leaves(month_start: str, month_end: str, employee_filters: dict[str, str
LeaveApplication = frappe.qb.DocType("Leave Application")
Employee = frappe.qb.DocType("Employee")
- frappe.has_permission("Leave Application", "read", throw=True)
-
- query = (
- frappe.qb.select(
+ query = frappe.qb.get_query(
+ "Leave Application",
+ fields=[
LeaveApplication.name.as_("leave"),
LeaveApplication.employee,
LeaveApplication.leave_type,
LeaveApplication.from_date,
LeaveApplication.to_date,
- )
- .from_(LeaveApplication)
- .left_join(Employee)
- .on(LeaveApplication.employee == Employee.name)
- .where(
- (LeaveApplication.docstatus == 1)
- & (LeaveApplication.status == "Approved")
- & (LeaveApplication.from_date <= month_end)
- & (LeaveApplication.to_date >= month_start)
- )
+ ],
+ filters={
+ "docstatus": 1,
+ "status": "Approved",
+ "from_date": ("<=", month_end),
+ "to_date": (">=", month_start),
+ },
+ ignore_permissions=False,
)
+ query = query.left_join(Employee).on(LeaveApplication.employee == Employee.name)
+
for filter in employee_filters:
query = query.where(Employee[filter] == employee_filters[filter])
@@ -309,8 +308,9 @@ def get_shifts(
ShiftType = frappe.qb.DocType("Shift Type")
Employee = frappe.qb.DocType("Employee")
- query = (
- frappe.qb.select(
+ query = frappe.qb.get_query(
+ "Shift Assignment",
+ fields=[
ShiftAssignment.name,
ShiftAssignment.employee,
ShiftAssignment.shift_type,
@@ -322,17 +322,23 @@ def get_shifts(
ShiftType.start_time,
ShiftType.end_time,
ShiftType.color,
- )
- .from_(ShiftAssignment)
- .left_join(ShiftType)
+ ],
+ filters={
+ "docstatus": 1,
+ "start_date": ("<=", month_end),
+ },
+ ignore_permissions=False,
+ )
+
+ # end_date is open-ended (None for shifts with no defined end) — must be
+ # expressed as an OR, which the filters dict can't represent cleanly.
+ query = query.where((ShiftAssignment.end_date >= month_start) | (ShiftAssignment.end_date.isnull()))
+
+ query = (
+ query.left_join(ShiftType)
.on(ShiftAssignment.shift_type == ShiftType.name)
.left_join(Employee)
.on(ShiftAssignment.employee == Employee.name)
- .where(
- (ShiftAssignment.docstatus == 1)
- & (ShiftAssignment.start_date <= month_end)
- & ((ShiftAssignment.end_date >= month_start) | (ShiftAssignment.end_date.isnull()))
- )
)
for filter in employee_filters:
diff --git a/hrms/hr/doctype/goal/goal.py b/hrms/hr/doctype/goal/goal.py
index cad78df7fb..61cc4a3c7d 100644
--- a/hrms/hr/doctype/goal/goal.py
+++ b/hrms/hr/doctype/goal/goal.py
@@ -163,48 +163,50 @@ def update_goal_progress_in_appraisal(self):
@frappe.whitelist()
def get_children(doctype: str, parent: str, is_root: bool = False, **filters) -> list[dict]:
- Goal = frappe.qb.DocType(doctype)
-
- query = (
- frappe.qb.from_(Goal)
- .select(
- Goal.name.as_("value"),
- Goal.goal_name.as_("title"),
- Goal.is_group.as_("expandable"),
- Goal.status,
- Goal.employee,
- Goal.employee_name,
- Goal.appraisal_cycle,
- Goal.progress,
- Goal.kra,
- )
- .where(Goal.status != "Archived")
- )
+ query_filters = [["status", "!=", "Archived"]]
+ query_or_filters = []
if filters.get("employee"):
- query = query.where(Goal.employee == filters.get("employee"))
+ query_filters.append(["employee", "=", filters.get("employee")])
if filters.get("appraisal_cycle"):
- query = query.where(Goal.appraisal_cycle == filters.get("appraisal_cycle"))
+ query_filters.append(["appraisal_cycle", "=", filters.get("appraisal_cycle")])
if filters.get("goal"):
- query = query.where(Goal.parent_goal == filters.get("goal"))
+ query_filters.append(["parent_goal", "=", filters.get("goal")])
+
elif parent and not is_root:
- # via expand child
- query = query.where(Goal.parent_goal == parent)
+ query_filters.append(["parent_goal", "=", parent])
+
else:
- ifnull = CustomFunction("IFNULL", ["value", "default"])
- query = query.where(ifnull(Goal.parent_goal, "") == "")
+ query_filters.append(["parent_goal", "in", ["", None]])
if filters.get("date_range"):
- date_range = frappe.parse_json(filters.get("date_range"))
-
- query = query.where(
- (Goal.start_date.between(date_range[0], date_range[1]))
- & ((Goal.end_date.isnull()) | (Goal.end_date.between(date_range[0], date_range[1])))
- )
+ from_date, to_date = frappe.parse_json(filters.get("date_range"))
+ query_filters.append(["start_date", "between", [from_date, to_date]])
+
+ # or_filters
+ query_or_filters.append(["end_date", "is", "not set"])
+ query_or_filters.append(["end_date", "between", [from_date, to_date]])
+
+ goals = frappe.get_list(
+ doctype,
+ fields=[
+ "name as value",
+ "goal_name as title",
+ "is_group as expandable",
+ "status",
+ "employee",
+ "employee_name",
+ "appraisal_cycle",
+ "progress",
+ "kra",
+ ],
+ filters=query_filters,
+ or_filters=query_or_filters if query_or_filters else None,
+ order_by="employee, kra",
+ )
- goals = query.orderby(Goal.employee, Goal.kra).run(as_dict=True)
_update_goal_completion_status(goals)
return goals
diff --git a/hrms/locale/fa.po b/hrms/locale/fa.po
index de17166981..a311d63b2a 100644
--- a/hrms/locale/fa.po
+++ b/hrms/locale/fa.po
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: contact@frappe.io\n"
"POT-Creation-Date: 2026-05-31 10:16+0000\n"
-"PO-Revision-Date: 2026-06-01 14:00\n"
+"PO-Revision-Date: 2026-06-07 15:12\n"
"Last-Translator: contact@frappe.io\n"
"Language-Team: Persian\n"
"MIME-Version: 1.0\n"
@@ -297,7 +297,7 @@ msgstr "
| {0} | {1} |
"
#: hrms/hr/doctype/job_requisition/job_requisition.js:101
msgid "A Job Requisition already exists for {0} requested by {1} for the same department.
Do you want to continue?"
-msgstr ""
+msgstr "یک درخواست شغل برای {0} که توسط {1} برای همان بخش درخواست شده است، از قبل وجود دارد.
آیا میخواهید ادامه دهید؟"
#: hrms/controllers/employee_reminders.py:123
#: hrms/controllers/employee_reminders.py:216
@@ -5439,7 +5439,7 @@ msgstr "زمانهای شیفت نامعتبر است"
#: hrms/api/roster.py:31
msgid "Invalid employee filter: {0}"
-msgstr ""
+msgstr "فیلتر کارمند نامعتبر: {0}"
#: hrms/hr/doctype/expense_claim/expense_claim.py:879
msgid "Invalid parameters provided. Please pass the required arguments."
@@ -5447,7 +5447,7 @@ msgstr "پارامترهای نامعتبر ارائه شده است. لطفا
#: hrms/api/roster.py:40
msgid "Invalid shift filter: {0}"
-msgstr ""
+msgstr "فیلتر شیفت نامعتبر: {0}"
#. Option for the 'Status' (Select) field in DocType 'Employee Grievance'
#: hrms/hr/doctype/employee_grievance/employee_grievance.json
diff --git a/hrms/locale/main.pot b/hrms/locale/main.pot
index 57041041f2..342b01302e 100644
--- a/hrms/locale/main.pot
+++ b/hrms/locale/main.pot
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Frappe HR VERSION\n"
"Report-Msgid-Bugs-To: contact@frappe.io\n"
-"POT-Creation-Date: 2026-05-31 10:16+0000\n"
-"PO-Revision-Date: 2026-05-31 10:16+0000\n"
+"POT-Creation-Date: 2026-06-07 10:18+0000\n"
+"PO-Revision-Date: 2026-06-07 10:18+0000\n"
"Last-Translator: contact@frappe.io\n"
"Language-Team: contact@frappe.io\n"
"MIME-Version: 1.0\n"
@@ -1383,8 +1383,8 @@ msgstr ""
msgid "Attendance To Date"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:187
-#: hrms/hr/doctype/attendance_request/attendance_request.py:206
+#: hrms/hr/doctype/attendance_request/attendance_request.py:189
+#: hrms/hr/doctype/attendance_request/attendance_request.py:208
msgid "Attendance Updated"
msgstr ""
@@ -1436,11 +1436,11 @@ msgstr ""
msgid "Attendance marked successfully."
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:225
+#: hrms/hr/doctype/attendance_request/attendance_request.py:227
msgid "Attendance not submitted for {0} as it is a Holiday."
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:234
+#: hrms/hr/doctype/attendance_request/attendance_request.py:236
msgid "Attendance not submitted for {0} as {1} is on leave."
msgstr ""
@@ -1535,11 +1535,11 @@ msgstr ""
msgid "Avg Feedback Score"
msgstr ""
-#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:218
+#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:220
msgid "Avg Utilization"
msgstr ""
-#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:224
+#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:226
msgid "Avg Utilization (Billed Only)"
msgstr ""
@@ -1637,7 +1637,7 @@ msgstr ""
msgid "Bill Amount"
msgstr ""
-#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:249
+#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:251
msgid "Billed Hours"
msgstr ""
@@ -1750,7 +1750,7 @@ msgstr ""
msgid "CTC"
msgstr ""
-#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:61
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:62
msgid "CTC Missing for Employee"
msgstr ""
@@ -1877,15 +1877,15 @@ msgstr ""
msgid "Cause of Grievance"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:192
+#: hrms/hr/doctype/attendance_request/attendance_request.py:194
msgid "Changed the Status for Other Half from {0} to {1} via Attendance Request as the status is Half Day"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:171
+#: hrms/hr/doctype/attendance_request/attendance_request.py:173
msgid "Changed the status from {0} to {1} and Status for Other Half to {2} via Attendance Request"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:175
+#: hrms/hr/doctype/attendance_request/attendance_request.py:177
msgid "Changed the status from {0} to {1} via Attendance Request"
msgstr ""
@@ -2035,7 +2035,7 @@ msgstr ""
msgid "Closing Notes"
msgstr ""
-#: frontend/src/views/Profile.vue:177
+#: frontend/src/views/Profile.vue:178
msgid "Company Information"
msgstr ""
@@ -2326,6 +2326,10 @@ msgstr ""
msgid "Current Openings"
msgstr ""
+#: frontend/src/views/ChangePassword.vue:22
+msgid "Current Password"
+msgstr ""
+
#. Label of the current_payroll_period (Link) field in DocType 'Salary Slip'
#: hrms/payroll/doctype/salary_slip/salary_slip.json
msgid "Current Payroll Period"
@@ -2635,7 +2639,7 @@ msgstr ""
msgid "Disable {0} or {1} to proceed."
msgstr ""
-#: frontend/src/views/AppSettings.vue:40
+#: frontend/src/views/AppSettings.vue:65
msgid "Disabling Push Notifications..."
msgstr ""
@@ -2989,7 +2993,7 @@ msgstr ""
#. Other Income'
#. Label of the section_break_24 (Section Break) field in DocType 'Payroll
#. Entry'
-#: frontend/src/views/Profile.vue:165
+#: frontend/src/views/Profile.vue:166
#: hrms/hr/doctype/employee_onboarding/employee_onboarding.json
#: hrms/hr/doctype/employee_performance_feedback/employee_performance_feedback.json
#: hrms/hr/doctype/exit_interview/exit_interview.json
@@ -3390,11 +3394,11 @@ msgstr ""
msgid "Employee was marked Absent for other half due to missing Employee Checkins."
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:480
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:479
msgid "Employee {0} : {1}"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:133
+#: hrms/hr/doctype/attendance_request/attendance_request.py:135
msgid "Employee {0} already has an Attendance Request {1} that overlaps with this period"
msgstr ""
@@ -3535,7 +3539,7 @@ msgstr ""
msgid "Enable Late Entry Marking"
msgstr ""
-#: frontend/src/views/AppSettings.vue:25
+#: frontend/src/views/AppSettings.vue:50
msgid "Enable Push Notifications"
msgstr ""
@@ -3557,7 +3561,7 @@ msgstr ""
msgid "Enabled only for Employee Benefit components from Salary Structure Assignment"
msgstr ""
-#: frontend/src/views/AppSettings.vue:40
+#: frontend/src/views/AppSettings.vue:65
msgid "Enabling Push Notifications..."
msgstr ""
@@ -3631,6 +3635,10 @@ msgstr ""
msgid "Enter yearly benefit amounts"
msgstr ""
+#: frontend/src/views/ForgotPassword.vue:22
+msgid "Enter your email address and we'll send you a link to reset your password."
+msgstr ""
+
#: frontend/src/components/FormField.vue:40
#: frontend/src/components/FormField.vue:52
msgid "Enter {0}"
@@ -4071,10 +4079,14 @@ msgstr ""
msgid "Failed to delete defaults for country {0}."
msgstr ""
-#: hrms/api/__init__.py:784
+#: hrms/api/__init__.py:796
msgid "Failed to download PDF: {0}"
msgstr ""
+#: frontend/src/views/ForgotPassword.vue:83
+msgid "Failed to send reset link"
+msgstr ""
+
#: hrms/hr/doctype/interview/interview.py:146
msgid "Failed to send the Interview Reschedule notification. Please configure your email account."
msgstr ""
@@ -4087,6 +4099,10 @@ msgstr ""
msgid "Failed to submit some leave policy assignments:"
msgstr ""
+#: frontend/src/views/ChangePassword.vue:94
+msgid "Failed to update password"
+msgstr ""
+
#: hrms/hr/doctype/interview/interview.py:239
msgid "Failed to update the Job Applicant status"
msgstr ""
@@ -4341,7 +4357,7 @@ msgstr ""
msgid "Formula"
msgstr ""
-#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:227
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:229
msgid "Formula/Amount"
msgstr ""
@@ -4539,7 +4555,7 @@ msgstr ""
msgid "Go to Login"
msgstr ""
-#: frontend/src/views/Login.vue:72
+#: frontend/src/views/Login.vue:29
msgid "Go to Reset Password page"
msgstr ""
@@ -4877,15 +4893,15 @@ msgstr ""
msgid "Hourly Rate"
msgstr ""
-#: hrms/regional/india/utils.py:184
+#: hrms/regional/india/utils.py:183
msgid "House rent paid days overlapping with {0}"
msgstr ""
-#: hrms/regional/india/utils.py:162
+#: hrms/regional/india/utils.py:161
msgid "House rented dates required for exemption calculation"
msgstr ""
-#: hrms/regional/india/utils.py:165
+#: hrms/regional/india/utils.py:164
msgid "House rented dates should be atleast 15 days apart"
msgstr ""
@@ -6394,7 +6410,7 @@ msgstr ""
msgid "Lodging Required"
msgstr ""
-#: frontend/src/views/Profile.vue:107
+#: frontend/src/views/Profile.vue:106
msgid "Log Out"
msgstr ""
@@ -6412,7 +6428,7 @@ msgstr ""
msgid "Login Failed"
msgstr ""
-#: frontend/src/views/Login.vue:8
+#: frontend/src/views/Login.vue:38
msgid "Login to Frappe HR"
msgstr ""
@@ -6449,6 +6465,10 @@ msgstr ""
msgid "Mandatory fields required for this action:"
msgstr ""
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.js:213
+msgid "Mandatory fields required in {0}"
+msgstr ""
+
#. Option for the 'KRA Evaluation Method' (Select) field in DocType 'Appraisal
#. Cycle'
#: hrms/hr/doctype/appraisal_cycle/appraisal_cycle.json
@@ -6717,7 +6737,7 @@ msgstr ""
msgid "Missing Tax Slab"
msgstr ""
-#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:311
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:313
msgid "Missing value for filters"
msgstr ""
@@ -6865,6 +6885,10 @@ msgstr ""
msgid "New Leaves Allocated (In Days)"
msgstr ""
+#: frontend/src/views/ChangePassword.vue:112
+msgid "New passwords do not match"
+msgstr ""
+
#. Description of the 'Create Shifts After' (Date) field in DocType 'Shift
#. Schedule Assignment'
#: hrms/hr/doctype/shift_schedule_assignment/shift_schedule_assignment.json
@@ -7040,7 +7064,7 @@ msgstr ""
msgid "No leaves have been allocated."
msgstr ""
-#: frontend/src/views/Login.vue:53
+#: frontend/src/views/Login.vue:91
msgid "No login methods are available. Please contact your administrator."
msgstr ""
@@ -7096,7 +7120,7 @@ msgstr ""
msgid "Non Taxable Earnings"
msgstr ""
-#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:250
+#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:252
msgid "Non-Billed Hours"
msgstr ""
@@ -7183,11 +7207,11 @@ msgstr ""
msgid "Number of leaves eligible for encashment based on leave type settings"
msgstr ""
-#: frontend/src/views/Login.vue:88
+#: frontend/src/views/Login.vue:105
msgid "OTP Code"
msgstr ""
-#: frontend/src/views/Login.vue:79
+#: frontend/src/views/Login.vue:96
msgid "OTP Verification"
msgstr ""
@@ -7379,7 +7403,7 @@ msgstr ""
msgid "Overall Average Rating"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:138
+#: hrms/hr/doctype/attendance_request/attendance_request.py:140
msgid "Overlapping Attendance Request"
msgstr ""
@@ -7445,11 +7469,11 @@ msgstr ""
msgid "Overtime Slip"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:481
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:480
msgid "Overtime Slip Creation Error for {0}"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:492
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:491
msgid "Overtime Slip Creation Failed"
msgstr ""
@@ -7458,19 +7482,19 @@ msgstr ""
msgid "Overtime Slip Step"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:515
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:514
msgid "Overtime Slip Submission Error for {0}"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:526
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:525
msgid "Overtime Slip Submission Failed"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:521
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:520
msgid "Overtime Slip Submitted"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:485
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:484
msgid "Overtime Slip created for {0} employee(s)"
msgstr ""
@@ -7486,11 +7510,11 @@ msgstr ""
msgid "Overtime Slip:{0} has been created between {1} and {2}"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:487
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:486
msgid "Overtime Slips Created"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:519
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:518
msgid "Overtime Slips submitted for {0} employee(s)"
msgstr ""
@@ -7591,6 +7615,10 @@ msgstr ""
msgid "Password policy for Salary Slips is not set"
msgstr ""
+#: frontend/src/views/ForgotPassword.vue:74
+msgid "Password reset link has been sent to your email."
+msgstr ""
+
#. Label of the pay_details_tab (Tab Break) field in DocType 'Job Opening'
#. Label of the pay_details_section (Section Break) field in DocType 'Job
#. Opening Template'
@@ -7869,7 +7897,7 @@ msgstr ""
msgid "Percent Deduction"
msgstr ""
-#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:250
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:252
msgid "Percent of CTC (%)"
msgstr ""
@@ -7927,10 +7955,22 @@ msgstr ""
msgid "Please enable default incoming account before creating Daily Work Summary Group"
msgstr ""
+#: frontend/src/views/ForgotPassword.vue:109
+msgid "Please enter a valid email address"
+msgstr ""
+
#: hrms/hr/doctype/staffing_plan/staffing_plan.js:97
msgid "Please enter the designation"
msgstr ""
+#: frontend/src/views/ForgotPassword.vue:104
+msgid "Please enter your email address"
+msgstr ""
+
+#: frontend/src/views/ChangePassword.vue:107
+msgid "Please fill all fields"
+msgstr ""
+
#: hrms/hr/doctype/overtime_slip/overtime_slip.js:11
msgid "Please fill in Employee, Posting Date, and Company before fetching overtime details."
msgstr ""
@@ -8083,7 +8123,7 @@ msgstr ""
msgid "Please set account in Salary Component {0}"
msgstr ""
-#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:53
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:54
msgid "Please set cost to company(CTC) for employee {0} in the {1}"
msgstr ""
@@ -8140,7 +8180,7 @@ msgstr ""
msgid "Please set {0} for the Employee: {1}"
msgstr ""
-#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:310
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:312
msgid "Please set {0} to get CTC report"
msgstr ""
@@ -8385,15 +8425,15 @@ msgstr ""
msgid "Purpose of Travel"
msgstr ""
-#: frontend/src/views/AppSettings.vue:128
+#: frontend/src/views/AppSettings.vue:152
msgid "Push Notification permission denied"
msgstr ""
-#: frontend/src/views/AppSettings.vue:96
+#: frontend/src/views/AppSettings.vue:121
msgid "Push notifications disabled"
msgstr ""
-#: frontend/src/views/AppSettings.vue:80
+#: frontend/src/views/AppSettings.vue:106
msgid "Push notifications have been disabled on your site"
msgstr ""
@@ -8991,7 +9031,7 @@ msgstr ""
msgid "Salary Expectation"
msgstr ""
-#: frontend/src/views/Profile.vue:200
+#: frontend/src/views/Profile.vue:201
msgid "Salary Information"
msgstr ""
@@ -9165,7 +9205,7 @@ msgstr ""
#. Label of a Workspace Sidebar Item
#: hrms/payroll/doctype/income_tax_slab/income_tax_slab.js:8
#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
-#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.js:34
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.js:51
#: hrms/payroll/workspace/payroll/payroll.json
#: hrms/workspace_sidebar/payroll.json
msgid "Salary Structure Assignment"
@@ -9479,6 +9519,10 @@ msgstr ""
msgid "Send Leave Notification"
msgstr ""
+#: frontend/src/views/ForgotPassword.vue:45
+msgid "Send Reset Link"
+msgstr ""
+
#. Label of the sender_copy (Link) field in DocType 'Payroll Settings'
#: hrms/payroll/doctype/payroll_settings/payroll_settings.json
msgid "Sender Copy"
@@ -10726,7 +10770,7 @@ msgstr ""
msgid "Total Net Pay"
msgstr ""
-#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:228
+#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:230
msgid "Total Non-Billed Hours"
msgstr ""
@@ -11132,7 +11176,7 @@ msgstr ""
msgid "Unsubmitted Appraisals"
msgstr ""
-#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:251
+#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:253
msgid "Untracked Hours"
msgstr ""
@@ -11185,11 +11229,11 @@ msgstr ""
msgid "Update Tax"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:198
+#: hrms/hr/doctype/attendance_request/attendance_request.py:200
msgid "Updated Status for Other Half from {0} to {1} for date {2} in the attendance record {3}"
msgstr ""
-#: hrms/hr/doctype/attendance_request/attendance_request.py:181
+#: hrms/hr/doctype/attendance_request/attendance_request.py:183
msgid "Updated status from {0} to {1} for date {2} in the attendance record {3}"
msgstr ""
@@ -11568,7 +11612,7 @@ msgstr ""
msgid "You can only submit Leave Encashment for a valid encashment amount"
msgstr ""
-#: hrms/api/__init__.py:741
+#: hrms/api/__init__.py:751
msgid "You can only upload JPG, PNG, PDF, TXT or Microsoft documents."
msgstr ""
@@ -11625,7 +11669,11 @@ msgstr ""
msgid "Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}"
msgstr ""
-#: frontend/src/views/Login.vue:63
+#: frontend/src/views/ChangePassword.vue:85
+msgid "Your password has been updated."
+msgstr ""
+
+#: frontend/src/views/Login.vue:22
msgid "Your password has expired. Please reset your password to continue"
msgstr ""
@@ -11661,7 +11709,7 @@ msgstr ""
msgid "here"
msgstr ""
-#: frontend/src/views/Login.vue:16
+#: frontend/src/views/Login.vue:46
msgid "johndoe@mail.com"
msgstr ""
@@ -11724,7 +11772,7 @@ msgstr ""
msgid "{0} & {1} more"
msgstr ""
-#: hrms/hr/doctype/overtime_slip/overtime_slip.py:513
+#: hrms/hr/doctype/overtime_slip/overtime_slip.py:512
msgid "{0} : {1}"
msgstr ""
diff --git a/hrms/locale/sv.po b/hrms/locale/sv.po
index 75768d57f3..f261c72e85 100644
--- a/hrms/locale/sv.po
+++ b/hrms/locale/sv.po
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: contact@frappe.io\n"
"POT-Creation-Date: 2026-05-31 10:16+0000\n"
-"PO-Revision-Date: 2026-06-01 14:00\n"
+"PO-Revision-Date: 2026-06-05 14:58\n"
"Last-Translator: contact@frappe.io\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@@ -2267,7 +2267,7 @@ msgstr "Skapa Utvärderingar"
#: hrms/payroll/doctype/payroll_entry/payroll_entry.js:458
msgid "Creating Payment Entries......"
-msgstr "......Skapar Betalning Poster......"
+msgstr "Skapar Kontering Poster......"
#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1611
msgid "Creating Salary Slips..."
diff --git a/hrms/payroll/doctype/employee_incentive/employee_incentive.js b/hrms/payroll/doctype/employee_incentive/employee_incentive.js
index 0f73d99b55..49de854752 100644
--- a/hrms/payroll/doctype/employee_incentive/employee_incentive.js
+++ b/hrms/payroll/doctype/employee_incentive/employee_incentive.js
@@ -47,7 +47,7 @@ frappe.ui.form.on("Employee Incentive", {
if (!frm.doc.company) return;
frm.set_query("salary_component", function () {
return {
- filters: { type: "earning", company: frm.doc.company },
+ filters: { type: "earning" },
};
});
},
diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.py b/hrms/payroll/doctype/salary_slip/salary_slip.py
index 1fe9d09e02..4a7fd24a46 100644
--- a/hrms/payroll/doctype/salary_slip/salary_slip.py
+++ b/hrms/payroll/doctype/salary_slip/salary_slip.py
@@ -2543,6 +2543,7 @@ def get_salary_component_data(component):
"is_flexible_benefit",
"variable_based_on_taxable_salary",
"accrual_component",
+ "exempted_from_income_tax",
),
as_dict=1,
cache=True,
diff --git a/hrms/payroll/doctype/salary_slip/test_salary_slip.py b/hrms/payroll/doctype/salary_slip/test_salary_slip.py
index 2342dfce84..bb5dd0f53f 100644
--- a/hrms/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/hrms/payroll/doctype/salary_slip/test_salary_slip.py
@@ -1533,6 +1533,178 @@ def test_income_tax_breakup_when_tax_added_via_additional_salary(self):
self.assertEqual(flt(salary_slip.future_income_tax_deductions, 2), 124843.25) # as 136843.25 - 12000
self.assertEqual(flt(salary_slip.total_income_tax, 2), 136843.25)
+ def test_income_tax_unchanged_on_submit_with_exempted_additional_deduction(self):
+ from hrms.payroll.doctype.payroll_entry.test_payroll_entry import make_payroll_entry
+ from hrms.payroll.doctype.salary_structure.test_salary_structure import (
+ create_salary_structure_assignment,
+ make_salary_structure,
+ )
+ from hrms.tests.test_utils import create_department
+
+ frappe.db.delete("Income Tax Slab", {"currency": "INR"})
+ # Two dedicated departments so the Payroll Entry below can filter to only emp2.
+ emp1_department = create_department("Issue 70232 Direct Submission Department")
+ emp2_department = create_department("Issue 70232 Payroll Entry Department")
+
+ emp = make_employee(
+ "test_employee_ss_exempted_addnl_deduction@salary.com",
+ company="_Test Company",
+ department=emp1_department,
+ date_of_joining="2021-01-01",
+ )
+ # Force the department even if the employee existed from a prior test run.
+ frappe.db.set_value("Employee", emp, "department", emp1_department)
+
+ # Clean up any state left from prior runs of this test so the slip/payroll-entry
+ # inserts below don't collide with the "already created for this period" check.
+ for table in ("Salary Slip", "Additional Salary", "Salary Structure Assignment"):
+ frappe.db.sql(f"DELETE FROM `tab{table}` WHERE employee=%s", emp)
+
+ payroll_period = frappe.get_doc("Payroll Period", "_Test Payroll Period")
+
+ create_tax_slab(
+ payroll_period,
+ effective_date=payroll_period.start_date,
+ allow_tax_exemption=True,
+ currency="INR",
+ )
+
+ salary_structure_name = "Test Salary Structure - Exempted Additional Deduction"
+ if frappe.db.exists("Salary Structure", salary_structure_name):
+ frappe.db.delete("Salary Structure", salary_structure_name)
+
+ salary_structure_doc = make_salary_structure(
+ salary_structure_name,
+ "Monthly",
+ company="_Test Company",
+ employee=emp,
+ from_date=payroll_period.start_date,
+ payroll_period=payroll_period,
+ test_tax=True,
+ base=200000, # high base so TDS is non-zero under the default slabs
+ )
+
+ # Deduction component marked as exempted from income tax (e.g., Car Lease Deduction).
+ exempted_component = "Car Lease Deduction"
+ make_salary_component(
+ [
+ {
+ "salary_component": exempted_component,
+ "abbr": "CLD",
+ "type": "Deduction",
+ "exempted_from_income_tax": 1,
+ "depends_on_payment_days": 0,
+ }
+ ],
+ False,
+ company_list=["_Test Company"],
+ )
+
+ def create_exempted_additional_salary(employee):
+ frappe.get_doc(
+ {
+ "doctype": "Additional Salary",
+ "employee": employee,
+ "company": "_Test Company",
+ "salary_component": exempted_component,
+ "amount": 25000,
+ "type": "Deduction",
+ "payroll_date": payroll_period.start_date,
+ "currency": "INR",
+ }
+ ).submit()
+
+ create_exempted_additional_salary(emp)
+
+ salary_slip = make_salary_slip(
+ salary_structure_doc.name, employee=emp, posting_date=payroll_period.start_date
+ )
+
+ def get_tds(doc):
+ for d in doc.deductions:
+ if d.salary_component == "TDS":
+ return flt(d.amount, 2)
+ return 0.0
+
+ salary_slip.submit()
+ salary_slip.reload()
+
+ tds_after_save = get_tds(salary_slip)
+ current_month_tax_after = flt(salary_slip.current_month_income_tax, 2)
+ total_income_tax_after = flt(salary_slip.total_income_tax, 2)
+ annual_taxable_after = flt(salary_slip.annual_taxable_amount, 2)
+ deductions_before_tax_after = flt(salary_slip.deductions_before_tax_calculation, 2)
+
+ # Reproduce the same scenario via Payroll Entry for a second employee
+ # and verify both submitted slips produce identical income tax values.
+ default_payable = frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account")
+ if not default_payable:
+ create_account(
+ account_name="_Test Payroll Payable",
+ company="_Test Company",
+ parent_account="Current Liabilities - _TC",
+ account_type="Payable",
+ )
+ default_payable = "_Test Payroll Payable - _TC"
+ frappe.db.set_value(
+ "Company", "_Test Company", "default_payroll_payable_account", default_payable
+ )
+ if frappe.db.get_value("Account", default_payable, "account_type") != "Payable":
+ frappe.db.set_value("Account", default_payable, "account_type", "Payable")
+
+ emp2 = make_employee(
+ "test_employee2_ss_exempted_addnl_deduction@salary.com",
+ company="_Test Company",
+ department=emp2_department,
+ date_of_joining="2021-01-01",
+ )
+ # Force the department even if the employee existed from a prior test run.
+ frappe.db.set_value("Employee", emp2, "department", emp2_department)
+
+ # Clean up any state left from prior runs of this test for emp2 too.
+ for table in ("Salary Slip", "Additional Salary", "Salary Structure Assignment"):
+ frappe.db.sql(f"DELETE FROM `tab{table}` WHERE employee=%s", emp2)
+
+ create_salary_structure_assignment(
+ emp2,
+ salary_structure_doc.name,
+ from_date=payroll_period.start_date,
+ company="_Test Company",
+ currency="INR",
+ payroll_period=payroll_period,
+ base=200000,
+ )
+ create_exempted_additional_salary(emp2)
+
+ payroll_entry = make_payroll_entry(
+ start_date=payroll_period.start_date,
+ end_date=get_last_day(payroll_period.start_date),
+ payable_account=default_payable,
+ currency="INR",
+ company="_Test Company",
+ department=emp2_department,
+ cost_center="Main - _TC",
+ )
+
+ emp2_slip_name = frappe.db.get_value(
+ "Salary Slip", {"payroll_entry": payroll_entry.name, "employee": emp2}
+ )
+ self.assertTrue(
+ emp2_slip_name,
+ "Payroll Entry did not create a Salary Slip for the second employee",
+ )
+ emp2_slip = frappe.get_doc("Salary Slip", emp2_slip_name)
+
+ self.assertEqual(
+ get_tds(emp2_slip),
+ tds_after_save,
+ "TDS on Payroll-Entry-submitted slip does not match the directly-submitted slip",
+ )
+ self.assertEqual(flt(emp2_slip.current_month_income_tax, 2), current_month_tax_after)
+ self.assertEqual(flt(emp2_slip.total_income_tax, 2), total_income_tax_after)
+ self.assertEqual(flt(emp2_slip.annual_taxable_amount, 2), annual_taxable_after)
+ self.assertEqual(flt(emp2_slip.deductions_before_tax_calculation, 2), deductions_before_tax_after)
+
def test_consistent_future_earnings_irrespective_of_payment_days(self):
"""
For CTC calculation, verifies that future non taxable earnings remain