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"],