diff --git a/hrms/api/roster.py b/hrms/api/roster.py
index 245a0395c7..11719fb16e 100644
--- a/hrms/api/roster.py
+++ b/hrms/api/roster.py
@@ -8,6 +8,39 @@
from hrms.hr.doctype.shift_assignment_tool.shift_assignment_tool import create_shift_assignment
from hrms.hr.doctype.shift_schedule.shift_schedule import get_or_insert_shift_schedule
+ALLOWED_EMPLOYEE_FILTERS = {
+ "status",
+ "company",
+ "department",
+ "branch",
+ "designation",
+ "employee_name",
+}
+
+ALLOWED_SHIFT_FILTERS = {
+ "shift_type",
+ "status",
+ "shift_location",
+}
+
+
+def _validate_employee_filters(employee_filters: dict[str, str]) -> None:
+ for key in employee_filters:
+ if key not in ALLOWED_EMPLOYEE_FILTERS:
+ frappe.throw(
+ _("Invalid employee filter: {0}").format(frappe.bold(key)),
+ frappe.PermissionError,
+ )
+
+
+def _validate_shift_filters(shift_filters: dict[str, str]) -> None:
+ for key in shift_filters:
+ if key not in ALLOWED_SHIFT_FILTERS:
+ frappe.throw(
+ _("Invalid shift filter: {0}").format(frappe.bold(key)),
+ frappe.PermissionError,
+ )
+
@frappe.whitelist()
def get_default_company() -> str:
@@ -18,6 +51,8 @@ def get_default_company() -> str:
def get_events(
month_start: str, month_end: str, employee_filters: dict[str, str], shift_filters: dict[str, str]
) -> dict[str, list[dict]]:
+ _validate_employee_filters(employee_filters)
+ _validate_shift_filters(shift_filters)
holidays = get_holidays(month_start, month_end, employee_filters)
leaves = get_leaves(month_start, month_end, employee_filters)
shifts = get_shifts(month_start, month_end, employee_filters, shift_filters)
@@ -54,6 +89,8 @@ def create_shift_schedule_assignment(
frequency: str,
shift_location: str | None = None,
) -> None:
+ frappe.has_permission("Employee", "read", employee, throw=True)
+ frappe.has_permission("Shift Schedule Assignment", "create", throw=True)
shift_schedule = get_or_insert_shift_schedule(shift_type, frequency, repeat_on_days)
shift_schedule_assignment = frappe.get_doc(
{
@@ -77,14 +114,19 @@ def create_shift_schedule_assignment(
@frappe.whitelist()
def delete_shift_schedule_assignment(shift_schedule_assignment: str) -> None:
- for shift_assignment in frappe.get_all(
+ shift_schedule_assignment_doc = frappe.get_doc("Shift Schedule Assignment", shift_schedule_assignment)
+ shift_schedule_assignment_doc.check_permission("delete")
+
+ for shift_assignment_name in frappe.get_all(
"Shift Assignment", {"shift_schedule_assignment": shift_schedule_assignment}, pluck="name"
):
- doc = frappe.get_doc("Shift Assignment", shift_assignment)
- if doc.docstatus == 1:
- doc.cancel()
- frappe.delete_doc("Shift Assignment", shift_assignment)
- frappe.delete_doc("Shift Schedule Assignment", shift_schedule_assignment)
+ shift_assignment_doc = frappe.get_doc("Shift Assignment", shift_assignment_name)
+ frappe.has_permission("Employee", "read", shift_assignment_doc.employee, throw=True)
+ shift_assignment_doc.check_permission("cancel" if shift_assignment_doc.docstatus == 1 else "delete")
+ if shift_assignment_doc.docstatus == 1:
+ shift_assignment_doc.cancel()
+ frappe.delete_doc("Shift Assignment", shift_assignment_name, ignore_permissions=True)
+ frappe.delete_doc("Shift Schedule Assignment", shift_schedule_assignment, ignore_permissions=True)
@frappe.whitelist()
@@ -94,14 +136,22 @@ def swap_shift(
if src_shift == tgt_shift:
frappe.throw(_("Source and target shifts cannot be the same"))
+ src_shift_doc = frappe.get_doc("Shift Assignment", src_shift)
+ frappe.has_permission("Employee", "read", src_shift_doc.employee, throw=True)
+ src_shift_doc.check_permission("write")
+
+ frappe.has_permission("Employee", "read", tgt_employee, throw=True)
+ frappe.has_permission("Shift Assignment", "create", throw=True)
+
if tgt_shift:
tgt_shift_doc = frappe.get_doc("Shift Assignment", tgt_shift)
+ frappe.has_permission("Employee", "read", tgt_shift_doc.employee, throw=True)
+ tgt_shift_doc.check_permission("write")
tgt_company = tgt_shift_doc.company
break_shift(tgt_shift_doc, tgt_date)
else:
tgt_company = frappe.db.get_value("Employee", tgt_employee, "company")
- src_shift_doc = frappe.get_doc("Shift Assignment", src_shift)
break_shift(src_shift_doc, src_date)
insert_shift(
tgt_employee,
@@ -130,6 +180,9 @@ def break_shift(assignment: str | ShiftAssignment, date: str) -> None:
if isinstance(assignment, str):
assignment = frappe.get_doc("Shift Assignment", assignment)
+ frappe.has_permission("Employee", "read", assignment.employee, throw=True)
+ assignment.check_permission("write")
+
if assignment.end_date and date_diff(assignment.end_date, date) < 0:
frappe.throw(_("Cannot break shift after end date"))
if date_diff(assignment.start_date, date) > 0:
@@ -165,6 +218,8 @@ def insert_shift(
status: str,
shift_location: str | None = None,
) -> None:
+ frappe.has_permission("Employee", "read", employee, throw=True)
+ frappe.has_permission("Shift Assignment", "create", throw=True)
filters = {
"doctype": "Shift Assignment",
"employee": employee,
@@ -193,6 +248,7 @@ def insert_shift(
def get_holidays(month_start: str, month_end: str, employee_filters: dict[str, str]) -> dict[str, list[dict]]:
+ _validate_employee_filters(employee_filters)
holidays = {}
holiday_lists = {}
@@ -213,6 +269,7 @@ def get_holidays(month_start: str, month_end: str, employee_filters: dict[str, s
def get_leaves(month_start: str, month_end: str, employee_filters: dict[str, str]) -> dict[str, list[dict]]:
+ _validate_employee_filters(employee_filters)
LeaveApplication = frappe.qb.DocType("Leave Application")
Employee = frappe.qb.DocType("Employee")
@@ -244,6 +301,8 @@ def get_leaves(month_start: str, month_end: str, employee_filters: dict[str, str
def get_shifts(
month_start: str, month_end: str, employee_filters: dict[str, str], shift_filters: dict[str, str]
) -> dict[str, list[dict]]:
+ _validate_employee_filters(employee_filters)
+ _validate_shift_filters(shift_filters)
ShiftAssignment = frappe.qb.DocType("Shift Assignment")
ShiftType = frappe.qb.DocType("Shift Type")
Employee = frappe.qb.DocType("Employee")
diff --git a/hrms/hr/doctype/attendance_request/attendance_request.py b/hrms/hr/doctype/attendance_request/attendance_request.py
index a74a3aa7ec..e0180d5db2 100644
--- a/hrms/hr/doctype/attendance_request/attendance_request.py
+++ b/hrms/hr/doctype/attendance_request/attendance_request.py
@@ -186,6 +186,25 @@ def create_or_update_attendance(self, date: str):
),
title=_("Attendance Updated"),
)
+ elif status == "Half Day" and doc.half_day_status == "Absent" and self.half_day:
+ old_half_day_status = doc.half_day_status
+ doc.db_set({"half_day_status": "Present", "attendance_request": self.name})
+ text = _(
+ "Changed the Status for Other Half from {0} to {1} via Attendance Request as the status is Half Day"
+ ).format(frappe.bold(old_half_day_status), frappe.bold("Present"))
+ doc.add_comment(comment_type="Info", text=text)
+
+ frappe.msgprint(
+ _(
+ "Updated Status for Other Half from {0} to {1} for date {2} in the attendance record {3}"
+ ).format(
+ frappe.bold(old_half_day_status),
+ frappe.bold("Present"),
+ frappe.bold(format_date(date)),
+ get_link_to_form("Attendance", doc.name),
+ ),
+ title=_("Attendance Updated"),
+ )
else:
# submit a new attendance record
doc = frappe.new_doc("Attendance")
@@ -221,6 +240,19 @@ def should_mark_attendance(self, attendance_date: str) -> bool:
return True
def has_leave_record(self, attendance_date: str) -> str | None:
+ filters = {
+ "employee": self.employee,
+ "docstatus": 1,
+ "from_date": ("<=", attendance_date),
+ "to_date": (">=", attendance_date),
+ "status": "Approved",
+ }
+ if self.half_day_date == attendance_date:
+ filters["half_day"] = 0
+
+ return frappe.db.exists("Leave Application", filters)
+
+ def has_half_day_leave_record(self, attendance_date: str) -> str | None:
return frappe.db.exists(
"Leave Application",
{
@@ -229,6 +261,8 @@ def has_leave_record(self, attendance_date: str) -> str | None:
"from_date": ("<=", attendance_date),
"to_date": (">=", attendance_date),
"status": "Approved",
+ "half_day": 1,
+ "half_day_date": attendance_date,
},
)
@@ -256,6 +290,8 @@ def status_unchanged(self, attendance_date):
new_status = self.get_attendance_status(attendance_date)
attendance_doc = self.get_attendance_doc(attendance_date)
if attendance_doc and attendance_doc.status == new_status:
+ if new_status == "Half Day" and self.half_day and attendance_doc.half_day_status == "Absent":
+ return False
return True
return False
@@ -277,7 +313,6 @@ def get_attendance_warnings(self) -> list:
for day in range(request_days):
attendance_date = add_days(self.from_date, day)
-
if not self.include_holidays and is_holiday(self.employee, attendance_date):
attendance_warnings.append({"date": attendance_date, "reason": "Holiday", "action": "Skip"})
elif self.has_leave_record(attendance_date):
diff --git a/hrms/hr/doctype/attendance_request/test_attendance_request.py b/hrms/hr/doctype/attendance_request/test_attendance_request.py
index 9c91208ff3..0ddbff4aab 100644
--- a/hrms/hr/doctype/attendance_request/test_attendance_request.py
+++ b/hrms/hr/doctype/attendance_request/test_attendance_request.py
@@ -117,9 +117,7 @@ def test_skip_attendance_on_leave(self):
self.to_date = get_year_ending(getdate())
frappe.delete_doc_if_exists("Leave Type", "Test Skip Attendance", force=1)
- leave_type = frappe.get_doc(
- dict(leave_type_name="Test Skip Attendance", doctype="Leave Type")
- ).insert()
+ leave_type = frappe.get_doc(leave_type_name="Test Skip Attendance", doctype="Leave Type").insert()
make_allocation_record(leave_type=leave_type.name, from_date=self.from_date, to_date=self.to_date)
today = getdate()
@@ -243,6 +241,99 @@ def test_half_day_status_change_when_existing_attendance_is_updated(self):
)
self.assertEqual(half_day_status, "Absent")
+ def test_half_day_absent_half_to_present(self):
+ """Test attendance request updates half_day_status from Absent to Present when existing Half Day attendance has the other half marked absent"""
+ today = getdate()
+
+ mark_attendance(self.employee.name, today, "Half Day", half_day_status="Absent")
+
+ attendance_request = frappe.get_doc(
+ {
+ "doctype": "Attendance Request",
+ "employee": self.employee.name,
+ "from_date": today,
+ "to_date": today,
+ "reason": "On Duty",
+ "half_day": 1,
+ "half_day_date": today,
+ "company": "_Test Company",
+ }
+ ).save()
+ attendance_request.submit()
+
+ updated = frappe.db.get_value(
+ "Attendance",
+ {"employee": self.employee.name, "attendance_date": today, "docstatus": 1},
+ ["status", "half_day_status"],
+ as_dict=True,
+ )
+ self.assertEqual(updated.status, "Half Day")
+ self.assertEqual(updated.half_day_status, "Present")
+
+ def test_half_day_with_shift_auto_absent(self):
+ """Test half-day attendance request when shift_type auto-flags the other half as absent due to missing checkins"""
+ from_date = get_year_start(add_months(getdate(), -1))
+ to_date = get_year_ending(getdate())
+ today = getdate()
+
+ frappe.delete_doc_if_exists("Leave Type", "Test Half Day Leave", force=1)
+ leave_type = frappe.get_doc(leave_type_name="Test Half Day Leave", doctype="Leave Type").insert()
+ make_allocation_record(leave_type=leave_type.name, from_date=from_date, to_date=to_date)
+ frappe.db.delete("Holiday", {"parent": self.holiday_list})
+
+ # 1) Submit half-day leave
+ leave_application = frappe.get_doc(
+ {
+ "doctype": "Leave Application",
+ "employee": self.employee.name,
+ "leave_type": leave_type.name,
+ "from_date": today,
+ "to_date": today,
+ "half_day": 1,
+ "half_day_date": today,
+ "status": "Approved",
+ }
+ ).insert()
+ leave_application.submit()
+
+ # 2) Create shift type + assignment
+ shift_type = create_shift("Test Half Day Shift", "09:00:00", "17:00:00")
+ shift_type.process_attendance_after = add_days(today, -1)
+ shift_type.last_sync_of_checkin = add_days(today, 1)
+ shift_type.enable_auto_attendance = 1
+ shift_type.save()
+ create_shift_assignment(self.employee.name, shift_type.name, add_days(today, -1), add_days(today, 1))
+
+ # 3) Attendance request for the other half — creates half-day attendance
+ attendance_request = frappe.get_doc(
+ {
+ "doctype": "Attendance Request",
+ "employee": self.employee.name,
+ "from_date": today,
+ "to_date": today,
+ "reason": "On Duty",
+ "half_day": 1,
+ "half_day_date": today,
+ "company": "_Test Company",
+ }
+ ).save()
+ attendance_request.submit()
+
+ # 4) Shift auto-attendance marks the other half absent when no checkins exist
+ frappe.get_doc("Shift Type", shift_type.name).mark_absent_for_half_day_dates(self.employee.name)
+
+ # Verify
+ attendance = frappe.db.get_value(
+ "Attendance",
+ {"attendance_request": attendance_request.name},
+ ["name", "status", "half_day_status", "modify_half_day_status"],
+ as_dict=True,
+ )
+ self.assertTrue(attendance)
+ self.assertEqual(attendance.status, "Half Day")
+ self.assertEqual(attendance.half_day_status, "Absent")
+ self.assertEqual(attendance.modify_half_day_status, 0)
+
@HRMSTestSuite.change_settings("HR Settings", {"allow_multiple_shift_assignments": True})
def test_overlap_with_different_shifts(self):
shift_1 = create_shift("Morning Shift", "08:00:00", "12:00:00")
diff --git a/hrms/hr/web_form/job_application/job_application.json b/hrms/hr/web_form/job_application/job_application.json
index 512ba5c555..c87aae31bb 100644
--- a/hrms/hr/web_form/job_application/job_application.json
+++ b/hrms/hr/web_form/job_application/job_application.json
@@ -1,36 +1,36 @@
{
- "accept_payment": 0,
"allow_comments": 1,
"allow_delete": 0,
- "allow_edit": 1,
+ "allow_edit": 0,
"allow_incomplete": 0,
- "allow_multiple": 1,
+ "allow_multiple": 0,
"allow_print": 0,
- "amount": 0.0,
- "amount_based_on_field": 0,
+ "anonymous": 0,
"apply_document_permissions": 0,
"client_script": "frappe.web_form.on('resume_link', (field, value) => {\n if (!frappe.utils.is_url(value)) {\n frappe.msgprint(__('Resume link not valid'));\n }\n});\n",
+ "condition_json": "[]",
"creation": "2016-09-10 02:53:16.598314",
"doc_type": "Job Applicant",
"docstatus": 0,
"doctype": "Web Form",
+ "hide_footer": 0,
+ "hide_navbar": 0,
"idx": 0,
"introduction_text": "",
"is_standard": 1,
+ "list_columns": [],
"login_required": 0,
"max_attachment_size": 0,
- "modified": "2020-10-07 19:27:17.143355",
+ "modified": "2026-05-20 11:17:50.641552",
"modified_by": "Administrator",
"module": "HR",
"name": "job-application",
"owner": "Administrator",
"published": 1,
"route": "job_application",
- "route_to_success_link": 0,
"show_attachments": 0,
- "show_in_grid": 0,
+ "show_list": 0,
"show_sidebar": 1,
- "sidebar_items": [],
"success_message": "Thank you for applying.",
"success_url": "/jobs",
"title": "Job Application",
@@ -123,6 +123,19 @@
"reqd": 0,
"show_in_filter": 0
},
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "resume_attachment",
+ "fieldtype": "Attach",
+ "hidden": 0,
+ "label": "Resume Attachment",
+ "max_length": 0,
+ "max_value": 0,
+ "precision": "",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
"allow_read_on_all_link_options": 0,
"fieldname": "",
@@ -197,4 +210,4 @@
"show_in_filter": 0
}
]
-}
\ No newline at end of file
+}
diff --git a/hrms/locale/main.pot b/hrms/locale/main.pot
index c16a88815b..f2588aedf7 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-17 10:03+0000\n"
-"PO-Revision-Date: 2026-05-17 10:03+0000\n"
+"POT-Creation-Date: 2026-05-24 10:09+0000\n"
+"PO-Revision-Date: 2026-05-24 10:09+0000\n"
"Last-Translator: contact@frappe.io\n"
"Language-Team: contact@frappe.io\n"
"MIME-Version: 1.0\n"
@@ -402,7 +402,7 @@ msgstr ""
msgid "Accrual Component must be set for Flexible Benefit Salary Components with accrual payout methods."
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:667
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:673
msgid "Accrual Journal Entry for salaries from {0} to {1}"
msgstr ""
@@ -846,6 +846,10 @@ msgstr ""
msgid "Annual Allocation Exceeded"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html:123
+msgid "Annual CTC"
+msgstr ""
+
#: hrms/setup.py:410
msgid "Annual Salary"
msgstr ""
@@ -1246,6 +1250,10 @@ msgstr ""
msgid "Assigning Structures..."
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html:108
+msgid "Assignment Date: "
+msgstr ""
+
#. Label of the from_date (Date) field in DocType 'Holiday List Assignment'
#: hrms/hr/doctype/holiday_list_assignment/holiday_list_assignment.json
msgid "Assignment Starts From"
@@ -1702,7 +1710,7 @@ msgstr ""
msgid "Bonus Payment Date cannot be a past date"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:273
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:279
msgid "Branch: {0}"
msgstr ""
@@ -1741,6 +1749,10 @@ msgstr ""
msgid "CTC"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:61
+msgid "CTC Missing for Employee"
+msgstr ""
+
#. Label of the calculate_final_score_based_on_formula (Check) field in DocType
#. 'Appraisal Cycle'
#: hrms/hr/doctype/appraisal_cycle/appraisal_cycle.json
@@ -1884,7 +1896,7 @@ msgstr ""
msgid "Check {1} for more details"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1582
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1588
msgid "Check Error Log {0} for more details."
msgstr ""
@@ -2119,7 +2131,7 @@ msgstr ""
msgid "Copy of Invitation/Announcement"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1647
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1653
msgid "Could not submit some Salary Slips: {}"
msgstr ""
@@ -2229,7 +2241,7 @@ msgstr ""
msgid "Creating Payment Entries......"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1605
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1611
msgid "Creating Salary Slips..."
msgstr ""
@@ -2265,7 +2277,7 @@ msgstr ""
msgid "Currency "
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:127
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:128
msgid "Currency of selected Income Tax Slab should be {0} instead of {1}"
msgstr ""
@@ -2562,7 +2574,7 @@ msgstr ""
msgid "Department {0} does not belong to company: {1}"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:275
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:281
msgid "Department: {0}"
msgstr ""
@@ -2595,7 +2607,7 @@ msgstr ""
msgid "Designation Skill"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:277
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:283
msgid "Designation: {0}"
msgstr ""
@@ -2925,6 +2937,13 @@ msgstr ""
msgid "Employee Boarding Activity"
msgstr ""
+#. Name of a report
+#. Label of a Workspace Sidebar Item
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.json
+#: hrms/workspace_sidebar/payroll.json
+msgid "Employee CTC Break-up"
+msgstr ""
+
#. Name of a DocType
#. Label of a Link in the HR Setup Workspace
#. Label of a Link in the Shift & Attendance Workspace
@@ -3575,7 +3594,7 @@ msgstr ""
msgid "End Date cannot be before Start Date"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:281
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:287
msgid "End date: {0}"
msgstr ""
@@ -4237,7 +4256,7 @@ msgstr ""
msgid "First Name "
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1565
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1571
msgid "Fiscal Year {0} not found"
msgstr ""
@@ -4321,6 +4340,10 @@ msgstr ""
msgid "Formula"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:227
+msgid "Formula/Amount"
+msgstr ""
+
#. Label of the fraction_of_applicable_earnings (Float) field in DocType
#. 'Gratuity Rule Slab'
#: hrms/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json
@@ -4361,7 +4384,7 @@ msgstr ""
msgid "From Date {0} cannot be after Payroll Period end date {1}"
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:92
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:93
msgid "From Date {0} cannot be after employee's relieving Date {1}"
msgstr ""
@@ -4369,7 +4392,7 @@ msgstr ""
msgid "From Date {0} cannot be before Payroll Period start date {1}"
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:84
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:85
msgid "From Date {0} cannot be before employee's joining Date {1}"
msgstr ""
@@ -5147,7 +5170,7 @@ msgstr ""
msgid "Income Tax Slab Other Charges"
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:112
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:113
msgid "Income Tax Slab is mandatory since the Salary Structure {0} has a tax component {1}"
msgstr ""
@@ -5159,6 +5182,10 @@ msgstr ""
msgid "Income Tax Slab not set in Salary Structure Assignment: {0}"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html:107
+msgid "Income Tax Slab: "
+msgstr ""
+
#: hrms/payroll/doctype/salary_slip/salary_slip.py:1958
msgid "Income Tax Slab: {0} is disabled"
msgstr ""
@@ -6659,11 +6686,11 @@ msgstr ""
msgid "Missing Advance Account"
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:118
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:119
msgid "Missing Mandatory Field"
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:200
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:201
msgid "Missing Opening Entries"
msgstr ""
@@ -6679,6 +6706,10 @@ msgstr ""
msgid "Missing Tax Slab"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:307
+msgid "Missing value for filters"
+msgstr ""
+
#. Label of the mode_of_travel (Select) field in DocType 'Travel Itinerary'
#: hrms/hr/doctype/travel_itinerary/travel_itinerary.json
msgid "Mode of Travel"
@@ -6958,11 +6989,11 @@ msgstr ""
msgid "No changes found in timings."
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:282
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:288
msgid "No employees found"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:265
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:271
msgid "No employees found for the mentioned criteria:
Company: {0}
Currency: {1}
Payroll Payable Account: {2}"
msgstr ""
@@ -7015,7 +7046,7 @@ msgstr ""
msgid "No replies from"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1633
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1639
msgid "No salary slip found to submit for the above selected criteria OR salary slip already submitted"
msgstr ""
@@ -7432,11 +7463,11 @@ msgstr ""
msgid "Overtime Slip created for {0} employee(s)"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1290
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1296
msgid "Overtime Slip creation is queued. It may take a few minutes"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1314
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1320
msgid "Overtime Slip submission is queued. It may take a few minutes"
msgstr ""
@@ -7614,7 +7645,7 @@ msgstr ""
msgid "Payment and Accounting"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1140
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1146
msgid "Payment of {0} from {1} to {2}"
msgstr ""
@@ -7827,6 +7858,10 @@ msgstr ""
msgid "Percent Deduction"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:250
+msgid "Percent of CTC (%)"
+msgstr ""
+
#. Label of a Desktop Icon
#. Name of a Workspace
#. Title of a Workspace Sidebar
@@ -8033,10 +8068,14 @@ msgstr ""
msgid "Please set a date range less than 90 days."
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:397
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:403
msgid "Please set account in Salary Component {0}"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:53
+msgid "Please set cost to company(CTC) for employee {0} in the {1}"
+msgstr ""
+
#: hrms/hr/doctype/leave_application/leave_application.py:712
msgid "Please set default template for Leave Approval Notification in HR Settings."
msgstr ""
@@ -8045,6 +8084,10 @@ msgstr ""
msgid "Please set default template for Leave Status Notification in HR Settings."
msgstr ""
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js:78
+msgid "Please set employee's total cost to company to see CTC breakup."
+msgstr ""
+
#: hrms/hr/doctype/employee_advance/employee_advance.py:74
msgid "Please set the Advance Account {0} or in {1}"
msgstr ""
@@ -8086,6 +8129,10 @@ msgstr ""
msgid "Please set {0} for the Employee: {1}"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py:306
+msgid "Please set {0} to get CTC report"
+msgstr ""
+
#: hrms/hr/doctype/shift_type/shift_type.js:21
#: hrms/hr/doctype/shift_type/shift_type.js:26
msgid "Please set {0}."
@@ -8107,7 +8154,7 @@ msgstr ""
msgid "Please specify the job applicant to be updated."
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:191
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:192
msgid "Please specify {0} and {1} (if any), for the correct tax calculation in future salary slips."
msgstr ""
@@ -8167,7 +8214,7 @@ msgstr ""
#: hrms/payroll/doctype/salary_structure/salary_structure.js:155
#: hrms/payroll/doctype/salary_structure/salary_structure.js:193
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js:73
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js:89
msgid "Preview Salary Slip"
msgstr ""
@@ -9027,7 +9074,7 @@ msgstr ""
msgid "Salary Slip already exists for {0} for the given dates"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:330
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:336
msgid "Salary Slip creation is queued. It may take a few minutes"
msgstr ""
@@ -9043,11 +9090,11 @@ msgstr ""
msgid "Salary Slip of employee {0} already created for time sheet {1}"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:375
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:381
msgid "Salary Slip submission is queued. It may take a few minutes"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1570
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1576
msgid "Salary Slip {0} failed for Payroll Entry {1}"
msgstr ""
@@ -9069,11 +9116,11 @@ msgstr ""
msgid "Salary Slips Submitted"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1612
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1618
msgid "Salary Slips already exist for employees {}, and will not be processed by this payroll."
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1639
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1645
msgid "Salary Slips submitted for period from {0} to {1}"
msgstr ""
@@ -9102,6 +9149,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/workspace/payroll/payroll.json
#: hrms/workspace_sidebar/payroll.json
msgid "Salary Structure Assignment"
@@ -9111,7 +9159,7 @@ msgstr ""
msgid "Salary Structure Assignment field"
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:79
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:80
msgid "Salary Structure Assignment for Employee already exists"
msgstr ""
@@ -9131,10 +9179,14 @@ msgstr ""
msgid "Salary Structure not assigned for employee {0} for date {1}"
msgstr ""
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:103
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:104
msgid "Salary Structure {0} does not belong to company {1}"
msgstr ""
+#: hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html:106
+msgid "Salary Structure: "
+msgstr ""
+
#: hrms/payroll/doctype/salary_component/salary_component.js:150
msgid "Salary Structures updated successfully"
msgstr ""
@@ -9224,6 +9276,10 @@ msgstr ""
msgid "Search for Jobs"
msgstr ""
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js:73
+msgid "See CTC Break-up"
+msgstr ""
+
#: hrms/hr/doctype/overtime_type/overtime_type.py:40
msgid "Select Applicable Components for Overtime Type"
msgstr ""
@@ -9247,7 +9303,7 @@ msgstr ""
msgid "Select Payment Account to make Bank Entry"
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1783
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1789
msgid "Select Payroll Frequency."
msgstr ""
@@ -9919,7 +9975,7 @@ msgstr ""
msgid "Start date cannot be greater than end date."
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:279
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:285
msgid "Start date: {0}"
msgstr ""
@@ -10017,7 +10073,7 @@ msgstr ""
msgid "Submitting Salary Slips and creating Journal Entry..."
msgstr ""
-#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1694
+#: hrms/payroll/doctype/payroll_entry/payroll_entry.py:1700
msgid "Submitting Salary Slips..."
msgstr ""
@@ -10077,7 +10133,7 @@ msgstr ""
#. Label of the tax_deducted_till_date (Currency) field in DocType 'Salary
#. Structure Assignment'
#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:195
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:196
msgid "Tax Deducted Till Date"
msgstr ""
@@ -10122,7 +10178,7 @@ msgstr ""
#. Label of the taxable_earnings_till_date (Currency) field in DocType 'Salary
#. Structure Assignment'
#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:194
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:195
msgid "Taxable Earnings Till Date"
msgstr ""
@@ -10287,7 +10343,7 @@ msgstr ""
#: hrms/payroll/doctype/additional_salary/additional_salary.py:82
#: hrms/payroll/doctype/employee_incentive/employee_incentive.py:39
-#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:240
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py:241
msgid "There is no Salary Structure assigned to {0}. First assign a Salary Structure."
msgstr ""
@@ -10515,6 +10571,11 @@ msgstr ""
msgid "Total Claimed Amount (Company Currency)"
msgstr ""
+#. Label of the ctc (Currency) field in DocType 'Salary Structure Assignment'
+#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+msgid "Total Cost To Company (CTC)"
+msgstr ""
+
#. Label of the lwp_days (Float) field in DocType 'Payroll Correction'
#: hrms/payroll/doctype/payroll_correction/payroll_correction.json
msgid "Total Days Without Pay"
diff --git a/hrms/locale/sv.po b/hrms/locale/sv.po
index 1b0cb29c55..1433780fd3 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-17 10:03+0000\n"
-"PO-Revision-Date: 2026-05-23 11:32\n"
+"PO-Revision-Date: 2026-05-24 11:43\n"
"Last-Translator: contact@frappe.io\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@@ -3089,7 +3089,7 @@ msgstr "Sjukförsäkring"
#. Label of a Workspace Sidebar Item
#: hrms/workspace_sidebar/shift_&_attendance.json
msgid "Employee Hours Utilization"
-msgstr "Personal Arbetstimmar Utnyttjande"
+msgstr "Personal Arbetstid Utnyttjande"
#. Name of a report
#. Label of a Link in the Shift & Attendance Workspace
@@ -9926,7 +9926,7 @@ msgstr "Standard Skatt Dispans Belopp"
#: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py:36
#: hrms/hr/report/project_profitability/project_profitability.py:102
msgid "Standard Working Hours"
-msgstr "Standard Arbets Timmar"
+msgstr "Standard Arbetstid"
#: hrms/payroll/doctype/salary_slip/salary_slip.py:1889
msgid "Start and end dates not in a valid Payroll Period, cannot calculate {0}."
diff --git a/hrms/payroll/doctype/salary_structure/test_salary_structure.py b/hrms/payroll/doctype/salary_structure/test_salary_structure.py
index 81e163528d..4e68bd3998 100644
--- a/hrms/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/hrms/payroll/doctype/salary_structure/test_salary_structure.py
@@ -148,9 +148,11 @@ def make_salary_structure(
test_accrual_component=False,
test_arrear=False,
test_salary_structure_arrear=False,
+ earnings=None,
+ deductions=None,
):
if not currency:
- currency = "INR" or "INR"
+ currency = "INR"
if frappe.db.exists("Salary Structure", salary_structure):
frappe.db.delete("Salary Structure", salary_structure)
@@ -167,14 +169,18 @@ def make_salary_structure(
"doctype": "Salary Structure",
"name": salary_structure,
"company": company or "_Test Company",
- "earnings": make_earning_salary_component(
+ "earnings": earnings
+ if earnings is not None
+ else make_earning_salary_component(
setup=True,
test_tax=test_tax,
company_list=["_Test Company"],
test_accrual_component=test_accrual_component,
test_arrear=test_arrear,
),
- "deductions": make_deduction_salary_component(
+ "deductions": deductions
+ if deductions is not None
+ else make_deduction_salary_component(
setup=True,
test_tax=test_tax,
company_list=["_Test Company"],