diff --git a/hrms/locale/main.pot b/hrms/locale/main.pot index da96726de6..c16a88815b 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-10 09:59+0000\n" -"PO-Revision-Date: 2026-05-10 09:59+0000\n" +"POT-Creation-Date: 2026-05-17 10:03+0000\n" +"PO-Revision-Date: 2026-05-17 10:03+0000\n" "Last-Translator: contact@frappe.io\n" "Language-Team: contact@frappe.io\n" "MIME-Version: 1.0\n" @@ -221,8 +221,8 @@ msgstr "" msgid "Example: SAL-{first_name}-{date_of_birth.year}
This will generate a password like SAL-Jane-1972" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:314 -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:322 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:315 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:323 msgid "Total Leaves Allocated are more than the number of days in the allocation period" msgstr "" @@ -341,7 +341,7 @@ msgstr "" msgid "Absent Records" msgstr "" -#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:56 +#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:58 msgid "Account No" msgstr "" @@ -460,7 +460,7 @@ msgstr "" msgid "Actual Overtime Duration" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:476 +#: hrms/hr/doctype/leave_application/leave_application.py:469 msgid "Actual balances aren't available because the leave application spans over different leave allocations. You can still apply for leaves which would be compensated during the next allocation." msgstr "" @@ -522,7 +522,7 @@ msgstr "" msgid "Additional Information " msgstr "" -#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:46 +#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:48 msgid "Additional PF" msgstr "" @@ -565,7 +565,7 @@ msgstr "" msgid "Adjust Allocation" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:452 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:453 msgid "Adjustment Created Successfully" msgstr "" @@ -579,15 +579,15 @@ msgstr "" msgid "Advance" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:83 +#: hrms/hr/doctype/employee_advance/employee_advance.py:78 msgid "Advance Account Required" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:87 +#: hrms/hr/doctype/employee_advance/employee_advance.py:82 msgid "Advance Account is mandatory. Please set the {0} in the Company {1} and submit this document." msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:130 +#: hrms/hr/doctype/employee_advance/employee_advance.py:125 msgid "Advance Account {} currency should be same as Salary Currency of Employee {}. Please select same currency Advance Account" msgstr "" @@ -603,7 +603,7 @@ msgstr "" msgid "Advanced Filters" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:476 +#: hrms/hr/doctype/expense_claim/expense_claim.py:464 msgid "All Exchange Gain/Loss amount of {0} has been booked through {1}" msgstr "" @@ -714,7 +714,7 @@ msgstr "" #. Label of the applicable_after (Int) field in DocType 'Leave Type' #: hrms/hr/doctype/leave_type/leave_type.json -msgid "Allow Leave Application After (Working Days)" +msgid "Allow Leave Application After (Calendar Days)" msgstr "" #. Label of the allow_multiple_shift_assignments (Check) field in DocType 'HR @@ -842,7 +842,7 @@ msgstr "" msgid "Annual Allocation" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:406 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:407 msgid "Annual Allocation Exceeded" msgstr "" @@ -925,11 +925,11 @@ msgstr "" msgid "Application Web Form Route" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:248 +#: hrms/hr/doctype/leave_application/leave_application.py:241 msgid "Application period cannot be across two allocation records" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:245 +#: hrms/hr/doctype/leave_application/leave_application.py:238 msgid "Application period cannot be outside leave allocation period" msgstr "" @@ -1117,7 +1117,7 @@ msgstr "" msgid "Approval Status" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:202 +#: hrms/hr/doctype/expense_claim/expense_claim.py:198 msgid "Approval Status must be 'Approved' or 'Rejected'" msgstr "" @@ -1399,7 +1399,7 @@ msgstr "" msgid "Attendance for employee {0} is already marked for the date {1}: {2}" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:641 +#: hrms/hr/doctype/leave_application/leave_application.py:634 msgid "Attendance for employee {0} is already marked for the following dates: {1}" msgstr "" @@ -1539,11 +1539,11 @@ msgstr "" msgid "Awaiting Response" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:216 +#: hrms/hr/doctype/leave_application/leave_application.py:209 msgid "Backdated Leave Application is restricted. Please set the {} in {}" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.js:290 +#: hrms/hr/doctype/expense_claim/expense_claim.js:281 msgid "Bank Entries" msgstr "" @@ -1776,15 +1776,15 @@ msgstr "" msgid "Cannot Modify Time" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:357 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:358 msgid "Cannot allocate leaves outside the allocation period {0} - {1}" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:486 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:487 msgid "Cannot allocate more leaves due to maximum leave allocation limit of {0} in leave policy assignment" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:478 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:479 msgid "Cannot allocate more leaves due to maximum leaves allowed limit of {0} in {1} leave type." msgstr "" @@ -1820,7 +1820,7 @@ msgstr "" msgid "Cannot create or change transactions against an Appraisal Cycle with status {0}." msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:658 +#: hrms/hr/doctype/leave_application/leave_application.py:651 msgid "Cannot find active Leave Period" msgstr "" @@ -1901,7 +1901,7 @@ msgstr "" msgid "Check Vacancies On Job Offer Creation" msgstr "" -#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:488 +#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:492 #: hrms/hr/utils.py:947 msgid "Check {0} for more details" msgstr "" @@ -1990,13 +1990,6 @@ msgstr "" msgid "Claims" msgstr "" -#. Option for the 'Status' (Select) field in DocType 'Interview' -#. Option for the 'Result' (Select) field in DocType 'Interview Feedback' -#: hrms/hr/doctype/interview/interview.json -#: hrms/hr/doctype/interview_feedback/interview_feedback.json -msgid "Cleared" -msgstr "" - #: hrms/payroll/doctype/salary_slip/salary_slip.js:300 msgid "Click {0} to change the configuration and then resave salary slip" msgstr "" @@ -2491,7 +2484,7 @@ msgstr "" msgid "Deduction Reports" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.js:84 +#: hrms/hr/doctype/employee_advance/employee_advance.js:79 msgid "Deduction from Salary" msgstr "" @@ -2565,7 +2558,7 @@ msgstr "" msgid "Department Wise Openings" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:161 +#: hrms/hr/doctype/expense_claim/expense_claim.py:157 msgid "Department {0} does not belong to company: {1}" msgstr "" @@ -3341,8 +3334,8 @@ msgstr "" msgid "Employee Transfer cannot be submitted before Transfer Date" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:120 -#: hrms/hr/doctype/employee_advance/employee_advance.py:190 +#: hrms/hr/doctype/employee_advance/employee_advance.py:115 +#: hrms/hr/doctype/employee_advance/employee_advance.py:185 msgid "Employee advance account {0} should be of type {1}." msgstr "" @@ -3397,7 +3390,7 @@ msgstr "" msgid "Employee {0} has already applied for Shift {1}: {2} that overlaps within this period" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:530 +#: hrms/hr/doctype/leave_application/leave_application.py:523 msgid "Employee {0} has already applied for {1} between {2} and {3} : {4}" msgstr "" @@ -3953,7 +3946,7 @@ msgstr "" #. Label of the expense_type (Data) field in DocType 'Expense Claim Type' #. Label of a Link in the Expenses Workspace #. Label of a Workspace Sidebar Item -#: hrms/hr/doctype/expense_claim/expense_claim.py:747 +#: hrms/hr/doctype/expense_claim/expense_claim.py:662 #: hrms/hr/doctype/expense_claim_detail/expense_claim_detail.json #: hrms/hr/doctype/expense_claim_type/expense_claim_type.json #: hrms/hr/workspace/expenses/expenses.json @@ -4028,7 +4021,7 @@ msgstr "" msgid "Expire Carry Forwarded Leaves (Days)" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:608 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:609 msgid "Expire Leaves" msgstr "" @@ -4070,7 +4063,7 @@ msgstr "" msgid "Failed to setup defaults for country {0}." msgstr "" -#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:485 +#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:489 msgid "Failed to submit some leave policy assignments:" msgstr "" @@ -4649,7 +4642,7 @@ msgstr "" #: frontend/src/components/SalarySlipItem.vue:13 #: hrms/payroll/doctype/salary_slip/salary_slip.json #: hrms/payroll/report/income_tax_deductions/income_tax_deductions.py:54 -#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:42 +#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:44 #: hrms/payroll/report/salary_register/salary_register.py:211 msgid "Gross Pay" msgstr "" @@ -4689,7 +4682,7 @@ msgstr "" #. Name of a DocType #. Label of a Link in the HR Setup Workspace #: hrms/hr/doctype/hr_settings/hr_settings.json -#: hrms/hr/doctype/leave_application/leave_application.py:218 +#: hrms/hr/doctype/leave_application/leave_application.py:211 #: hrms/hr/workspace/hr_setup/hr_setup.json msgid "HR Settings" msgstr "" @@ -4730,7 +4723,7 @@ msgstr "" msgid "Half Day Date" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:916 +#: hrms/hr/doctype/leave_application/leave_application.py:909 msgid "Half Day Date cannot be a holiday" msgstr "" @@ -4738,7 +4731,7 @@ msgstr "" msgid "Half Day Date is mandatory" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:919 +#: hrms/hr/doctype/leave_application/leave_application.py:912 msgid "Half Day Date should be between From Date and To Date" msgstr "" @@ -4870,7 +4863,7 @@ msgstr "" msgid "House rented dates should be atleast 15 days apart" msgstr "" -#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:60 +#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:62 msgid "IFSC" msgstr "" @@ -5200,11 +5193,11 @@ msgstr "" msgid "Install Frappe HR" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:489 +#: hrms/hr/doctype/leave_application/leave_application.py:482 msgid "Insufficient Balance" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:487 +#: hrms/hr/doctype/leave_application/leave_application.py:480 msgid "Insufficient leave balance for Leave Type {0}" msgstr "" @@ -5398,7 +5391,7 @@ msgstr "" msgid "Invalid Benefit Amounts" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:360 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:361 msgid "Invalid Dates" msgstr "" @@ -5419,7 +5412,7 @@ msgstr "" msgid "Invalid Shift Times" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:995 +#: hrms/hr/doctype/expense_claim/expense_claim.py:876 msgid "Invalid parameters provided. Please pass the required arguments." msgstr "" @@ -5882,7 +5875,7 @@ msgstr "" msgid "Leave Application" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:811 +#: hrms/hr/doctype/leave_application/leave_application.py:804 msgid "Leave Application period cannot be across two non-consecutive leave allocations {0} and {1}." msgstr "" @@ -5975,7 +5968,7 @@ msgstr "" msgid "Leave Block List Name" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:1447 +#: hrms/hr/doctype/leave_application/leave_application.py:1440 msgid "Leave Blocked" msgstr "" @@ -6168,7 +6161,7 @@ msgstr "" msgid "Leave Type {0} cannot be allocated since it is leave without pay" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:597 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:598 msgid "Leave Type {0} cannot be carry-forwarded" msgstr "" @@ -6187,7 +6180,7 @@ msgstr "" msgid "Leave Without Pay does not match with approved {} records" msgstr "" -#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:151 +#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:155 msgid "Leave allocation is skipped for {0}, because number of leaves to be allocated is 0." msgstr "" @@ -6207,7 +6200,7 @@ msgstr "" msgid "Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:286 +#: hrms/hr/doctype/leave_application/leave_application.py:279 msgid "Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}" msgstr "" @@ -6216,7 +6209,7 @@ msgstr "" msgid "Leave for optional holiday" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:557 +#: hrms/hr/doctype/leave_application/leave_application.py:550 msgid "Leave of type {0} cannot be longer than {1}." msgstr "" @@ -6270,7 +6263,7 @@ msgstr "" msgid "Leaves Allocated" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:602 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:603 msgid "Leaves Expired" msgstr "" @@ -6400,7 +6393,7 @@ msgstr "" msgid "Lower Range" msgstr "" -#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:61 +#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:63 msgid "MICR" msgstr "" @@ -6573,7 +6566,7 @@ msgstr "" msgid "Maximum Consecutive Leaves Allowed" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:567 +#: hrms/hr/doctype/leave_application/leave_application.py:560 msgid "Maximum Consecutive Leaves Exceeded" msgstr "" @@ -6656,13 +6649,13 @@ msgstr "" msgid "Minimum Year for Gratuity" msgstr "" -#. Description of the 'Allow Leave Application After (Working Days)' (Int) +#. Description of the 'Allow Leave Application After (Calendar Days)' (Int) #. field in DocType 'Leave Type' #: hrms/hr/doctype/leave_type/leave_type.json -msgid "Minimum working days required since Date of Joining to apply for this leave" +msgid "Minimum calendar days required since Date of Joining to apply for this leave" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:95 +#: hrms/hr/doctype/employee_advance/employee_advance.py:90 msgid "Missing Advance Account" msgstr "" @@ -6678,7 +6671,7 @@ msgstr "" msgid "Missing Relieving Date" msgstr "" -#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:18 +#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:20 msgid "Missing Salary Components" msgstr "" @@ -6691,7 +6684,7 @@ msgstr "" msgid "Mode of Travel" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:493 +#: hrms/hr/doctype/expense_claim/expense_claim.py:481 msgid "Mode of payment is required to make a payment" msgstr "" @@ -6766,7 +6759,7 @@ msgstr "" #. Label of the net_pay (Currency) field in DocType 'Salary Structure' #: hrms/payroll/doctype/salary_slip/salary_slip.json #: hrms/payroll/doctype/salary_structure/salary_structure.json -#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:49 +#: hrms/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py:51 #: hrms/payroll/report/salary_register/salary_register.py:251 msgid "Net Pay" msgstr "" @@ -6836,7 +6829,7 @@ msgstr "" msgid "New shift assignments will be created after this date." msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:426 +#: hrms/hr/doctype/employee_advance/employee_advance.py:380 msgid "No Bank/Cash Account found for currency {0}. Please create one under company {1}." msgstr "" @@ -7270,7 +7263,7 @@ msgstr "" msgid "Only interviewers can submit feedback" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:224 +#: hrms/hr/doctype/leave_application/leave_application.py:217 msgid "Only users with the {0} role can create backdated leave applications" msgstr "" @@ -7305,7 +7298,7 @@ msgstr "" msgid "Opening closed." msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:664 +#: hrms/hr/doctype/leave_application/leave_application.py:657 msgid "Optional Holiday List not set for leave period {0}" msgstr "" @@ -7336,7 +7329,7 @@ msgstr "" msgid "Outgoing Salary" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:326 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:327 msgid "Over Allocation" msgstr "" @@ -7500,15 +7493,15 @@ msgstr "" msgid "PAN Number" msgstr "" -#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:43 +#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:45 msgid "PF Account" msgstr "" -#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:44 +#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:46 msgid "PF Amount" msgstr "" -#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:51 +#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:53 msgid "PF Loan" msgstr "" @@ -7581,7 +7574,7 @@ msgstr "" msgid "Pay via Salary Slip" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:191 +#: hrms/hr/doctype/expense_claim/expense_claim.py:187 msgid "Payable Account is mandatory to submit an Expense Claim" msgstr "" @@ -8044,15 +8037,15 @@ msgstr "" msgid "Please set account in Salary Component {0}" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:719 +#: hrms/hr/doctype/leave_application/leave_application.py:712 msgid "Please set default template for Leave Approval Notification in HR Settings." msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:694 +#: hrms/hr/doctype/leave_application/leave_application.py:687 msgid "Please set default template for Leave Status Notification in HR Settings." msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:79 +#: hrms/hr/doctype/employee_advance/employee_advance.py:74 msgid "Please set the Advance Account {0} or in {1}" msgstr "" @@ -8060,7 +8053,7 @@ msgstr "" msgid "Please set the Appraisal Template for all the {0} or select the template in the Employees table below." msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.js:527 +#: hrms/hr/doctype/expense_claim/expense_claim.js:514 msgid "Please set the Company" msgstr "" @@ -8126,11 +8119,6 @@ msgstr "" msgid "Please update your status for this training event" msgstr "" -#. Label of the posted_on (Datetime) field in DocType 'Job Opening' -#: hrms/hr/doctype/job_opening/job_opening.json -msgid "Posted On" -msgstr "" - #. Label of the posting_date (Date) field in DocType 'Gratuity' #: hrms/payroll/doctype/gratuity/gratuity.json msgid "Posting date" @@ -8484,8 +8472,8 @@ msgstr "" msgid "Reduction is more than {0}'s available leave balance {1} for leave type {2}" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:405 -#: hrms/hr/doctype/leave_application/leave_application.py:561 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:406 +#: hrms/hr/doctype/leave_application/leave_application.py:554 #: hrms/payroll/doctype/additional_salary/additional_salary.py:218 #: hrms/payroll/doctype/payroll_entry/payroll_entry.py:141 msgid "Reference: {0}" @@ -8716,8 +8704,8 @@ msgstr "" msgid "Retirement Age (In Years)" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:481 -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:489 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:482 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:490 msgid "Retry Failed" msgstr "" @@ -8735,7 +8723,7 @@ msgstr "" msgid "Retrying allocations" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:236 +#: hrms/hr/doctype/employee_advance/employee_advance.py:231 msgid "Return amount cannot be greater than unclaimed amount" msgstr "" @@ -8763,7 +8751,7 @@ msgstr "" #. Label of the role_allowed_to_create_backdated_leave_application (Link) field #. in DocType 'HR Settings' #: hrms/hr/doctype/hr_settings/hr_settings.json -#: hrms/hr/doctype/leave_application/leave_application.py:217 +#: hrms/hr/doctype/leave_application/leave_application.py:210 msgid "Role Allowed to Create Backdated Leave Application" msgstr "" @@ -8813,11 +8801,11 @@ msgstr "" msgid "Row #{0}: Timesheet amount will overwrite the Earning component amount for the Salary Component {1}" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:963 +#: hrms/hr/doctype/expense_claim/expense_claim.py:844 msgid "Row No {0}: Amount cannot be greater than the Outstanding Amount against Expense Claim {1}. Outstanding Amount is {2}" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:561 +#: hrms/hr/doctype/expense_claim/expense_claim.py:549 msgid "Row {0}# Allocated amount {1} cannot be greater than unclaimed amount {2}" msgstr "" @@ -8829,7 +8817,7 @@ msgstr "" msgid "Row {0}# Paid Amount cannot be greater than Total amount" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:228 +#: hrms/hr/doctype/employee_advance/employee_advance.py:223 msgid "Row {0}# Paid Amount cannot be greater than requested advance amount" msgstr "" @@ -8849,7 +8837,7 @@ msgstr "" msgid "Row {0}: {1}" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:486 +#: hrms/hr/doctype/expense_claim/expense_claim.py:474 msgid "Row {0}: {1} is required in the expenses table to book an expense claim." msgstr "" @@ -9173,7 +9161,7 @@ msgstr "" msgid "Salary Withholding {0} already exists for employee {1} for the selected period" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:402 +#: hrms/hr/doctype/leave_application/leave_application.py:395 msgid "Salary already processed for period between {0} and {1}, Leave application period cannot be between this date range." msgstr "" @@ -9183,7 +9171,7 @@ msgstr "" msgid "Salary breakup based on Earning and Deduction." msgstr "" -#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:15 +#: hrms/payroll/report/provident_fund_deductions/provident_fund_deductions.py:17 msgid "Salary components of type Provident Fund, Additional Provident Fund or Provident Fund Loan are not set up." msgstr "" @@ -9210,7 +9198,7 @@ msgstr "" msgid "Sanctioned Amount (Company Currency)" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:585 +#: hrms/hr/doctype/expense_claim/expense_claim.py:573 msgid "Sanctioned Amount cannot be greater than Claim Amount in Row {0}." msgstr "" @@ -9286,7 +9274,7 @@ msgstr "" msgid "Select Users" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.js:572 +#: hrms/hr/doctype/expense_claim/expense_claim.js:559 msgid "Select an employee to get the employee advance." msgstr "" @@ -9344,11 +9332,11 @@ msgstr "" msgid "Select your Leave Approver i.e. the person who approves or rejects your leaves." msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.js:607 +#: hrms/hr/doctype/expense_claim/expense_claim.js:594 msgid "Selected employee advance is not of employee {0}" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:554 +#: hrms/hr/doctype/expense_claim/expense_claim.py:542 msgid "Selected employee advance is not of employee {}" msgstr "" @@ -9376,11 +9364,11 @@ msgstr "" msgid "Self-Study" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:175 +#: hrms/hr/doctype/expense_claim/expense_claim.py:171 msgid "Self-approval for Expense Claims is not allowed" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:902 +#: hrms/hr/doctype/leave_application/leave_application.py:895 msgid "Self-approval for leaves is not allowed" msgstr "" @@ -9498,7 +9486,7 @@ msgstr "" msgid "Set optional filters to fetch employees in the appraisee list" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:746 +#: hrms/hr/doctype/expense_claim/expense_claim.py:661 msgid "Set the default account for the {0} {1}" msgstr "" @@ -9981,7 +9969,7 @@ msgstr "" msgid "Submission Date" msgstr "" -#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:496 +#: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py:500 msgid "Submission Failed" msgstr "" @@ -10222,7 +10210,7 @@ msgstr "" msgid "The day of the month when leaves should be allocated" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:445 +#: hrms/hr/doctype/leave_application/leave_application.py:438 msgid "The day(s) on which you are applying for leave are holidays. You need not apply for leave." msgstr "" @@ -10353,10 +10341,6 @@ msgstr "" msgid "This is based on the attendance of this Employee" msgstr "" -#: hrms/www/hrms.py:20 -msgid "This method is only meant for developer mode" -msgstr "" - #: hrms/payroll/doctype/additional_salary/additional_salary.py:231 msgid "This will overwrite the tax component {0} in the salary slip and tax won't be calculated based on the Income Tax Slabs" msgstr "" @@ -10437,7 +10421,7 @@ msgid "To date can not greater than employee's relieving date" msgstr "" #: hrms/hr/doctype/leave_allocation/leave_allocation.py:214 -#: hrms/hr/doctype/leave_application/leave_application.py:230 +#: hrms/hr/doctype/leave_application/leave_application.py:223 msgid "To date cannot be before from date" msgstr "" @@ -10546,7 +10530,7 @@ msgstr "" #. Label of the total_deduction (Currency) field in DocType 'Salary Structure' #: hrms/payroll/doctype/salary_slip/salary_slip.json #: hrms/payroll/doctype/salary_structure/salary_structure.json -#: hrms/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py:148 +#: hrms/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py:155 #: hrms/payroll/report/salary_register/salary_register.py:244 msgid "Total Deduction" msgstr "" @@ -10611,7 +10595,7 @@ msgstr "" msgid "Total Goal Score" msgstr "" -#: hrms/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py:141 +#: hrms/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py:148 msgid "Total Gross Pay" msgstr "" @@ -10661,7 +10645,7 @@ msgstr "" msgid "Total Loan Repayment" msgstr "" -#: hrms/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py:155 +#: hrms/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py:162 msgid "Total Net Pay" msgstr "" @@ -10731,7 +10715,7 @@ msgstr "" msgid "Total Self Score" msgstr "" -#: hrms/hr/doctype/expense_claim/expense_claim.py:579 +#: hrms/hr/doctype/expense_claim/expense_claim.py:567 msgid "Total advance amount cannot be greater than total sanctioned amount" msgstr "" @@ -10753,11 +10737,11 @@ msgstr "" msgid "Total in words (Company Currency)" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:401 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:402 msgid "Total leaves allocated cannot exceed annual allocation of {0}." msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:287 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:288 msgid "Total leaves allocated is mandatory for Leave Type {0}" msgstr "" @@ -11156,11 +11140,6 @@ msgstr "" msgid "Upload images or documents" msgstr "" -#: frontend/src/components/FormView.vue:124 -#: frontend/src/components/FormView.vue:163 -msgid "Uploading..." -msgstr "" - #. Label of the upper_range (Currency) field in DocType 'Job Applicant' #. Label of the upper_range (Currency) field in DocType 'Job Opening' #. Label of the upper_range (Currency) field in DocType 'Job Opening Template' @@ -11214,13 +11193,6 @@ msgstr "" msgid "Value missing" msgstr "" -#. Label of the variable (Currency) field in DocType 'Salary Structure -#. Assignment' -#: hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js:185 -#: hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json -msgid "Variable" -msgstr "" - #. Label of the variable_based_on_taxable_salary (Check) field in DocType #. 'Salary Component' #. Label of the variable_based_on_taxable_salary (Check) field in DocType @@ -11297,15 +11269,15 @@ msgstr "" msgid "WARNING: Loan Management module has been separated from ERPNext." msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:472 +#: hrms/hr/doctype/leave_application/leave_application.py:465 msgid "Warning: Insufficient leave balance for Leave Type {0} in this allocation." msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:480 +#: hrms/hr/doctype/leave_application/leave_application.py:473 msgid "Warning: Insufficient leave balance for Leave Type {0}." msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:418 +#: hrms/hr/doctype/leave_application/leave_application.py:411 msgid "Warning: Leave application contains following block dates" msgstr "" @@ -11491,7 +11463,7 @@ msgstr "" msgid "Yes, Proceed" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:428 +#: hrms/hr/doctype/leave_application/leave_application.py:421 msgid "You are not authorized to approve leaves on Block Dates" msgstr "" @@ -11523,8 +11495,8 @@ msgstr "" msgid "You cannot reverse more than the total LWP days {0}. You have already reversed {1} days for this employee." msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:457 -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:620 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:458 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:621 msgid "You do not have permission to complete this action" msgstr "" @@ -11604,7 +11576,7 @@ msgstr "" msgid "created" msgstr "" -#: hrms/hr/doctype/employee_advance/employee_advance.py:80 +#: hrms/hr/doctype/employee_advance/employee_advance.py:75 msgid "here" msgstr "" @@ -11723,8 +11695,8 @@ msgstr "" msgid "{0} already has an active Shift Assignment {1} for some/all of these dates." msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:201 -msgid "{0} applicable after {1} working days" +#: hrms/hr/doctype/leave_application/leave_application.py:194 +msgid "{0} applicable after {1} calendar days" msgstr "" #: frontend/src/components/LeaveBalance.vue:37 @@ -11769,19 +11741,19 @@ msgstr "" msgid "{0} is not allowed to submit Interview Feedback for the Interview: {1}" msgstr "" -#: hrms/hr/doctype/leave_application/leave_application.py:672 +#: hrms/hr/doctype/leave_application/leave_application.py:665 msgid "{0} is not in Optional Holiday List" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:395 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:396 msgid "{0} leaves allocated successfully" msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:603 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:604 msgid "{0} leaves from allocation for {1} leave type have expired and will be processed during the next scheduled job. It is recommended to expire them now before creating new leave policy assignments." msgstr "" -#: hrms/hr/doctype/leave_allocation/leave_allocation.py:390 +#: hrms/hr/doctype/leave_allocation/leave_allocation.py:391 msgid "{0} leaves were manually allocated by {1} on {2}" msgstr "" diff --git a/hrms/payroll/doctype/payroll_entry/payroll_entry.py b/hrms/payroll/doctype/payroll_entry/payroll_entry.py index 109245251c..10516f2fe2 100644 --- a/hrms/payroll/doctype/payroll_entry/payroll_entry.py +++ b/hrms/payroll/doctype/payroll_entry/payroll_entry.py @@ -218,9 +218,13 @@ def cancel_linked_journal_entries(self): ) # cancel linked payment ledger entry for pl in journal_entry_payment_ledgers: - frappe.get_doc("Payment Ledger Entry", pl).cancel() + payment_ledger_entry = frappe.get_doc("Payment Ledger Entry", pl) + payment_ledger_entry.flags.ignore_permissions = True + payment_ledger_entry.cancel() - frappe.get_doc("Journal Entry", je).cancel() + journal_entry = frappe.get_doc("Journal Entry", je) + journal_entry.flags.ignore_permissions = True + journal_entry.cancel() def cancel_linked_payment_ledger_entries(self): payment_ledgers = frappe.get_all( @@ -231,7 +235,9 @@ def cancel_linked_payment_ledger_entries(self): # cancel payment ledger entry for pl in payment_ledgers: - frappe.get_doc("Payment Ledger Entry", pl).cancel() + payment_ledger_entry = frappe.get_doc("Payment Ledger Entry", pl) + payment_ledger_entry.flags.ignore_permissions = True + payment_ledger_entry.cancel() def get_linked_salary_slips(self): return frappe.get_all("Salary Slip", {"payroll_entry": self.name}, ["name", "docstatus"]) diff --git a/hrms/payroll/doctype/payroll_entry/test_payroll_entry.py b/hrms/payroll/doctype/payroll_entry/test_payroll_entry.py index cd78f4df9e..b61b5f8756 100644 --- a/hrms/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/hrms/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -444,6 +444,41 @@ def test_payroll_entry_cancellation(self): journal_entries = get_linked_journal_entries(payroll_entry.name, docstatus=2) self.assertEqual(len(journal_entries), 2) + def test_payroll_entry_cancellation_with_hr_manager(self): + company_doc = frappe.get_doc("Company", "_Test Company") + employee = make_employee("test_hr_manager_employee@payroll.com", company=company_doc.name) + + setup_salary_structure(employee, company_doc) + dates = get_start_end_dates("Monthly", nowdate()) + payroll_entry = make_payroll_entry( + start_date=dates.start_date, + end_date=dates.end_date, + payable_account=company_doc.default_payroll_payable_account, + currency=company_doc.default_currency, + company=company_doc.name, + cost_center="Main - _TC", + payment_account="Cash - _TC", + ) + + hr_user = frappe.get_doc( + { + "doctype": "User", + "email": "test_hr_manager@payroll.com", + "first_name": "Test HR Manager", + "enabled": 1, + } + ).insert(ignore_if_duplicate=True) + hr_user.add_roles("HR Manager") + frappe.set_user(hr_user.name) + + payroll_entry.submit() + self.assertEqual(payroll_entry.status, "Submitted") + + payroll_entry.cancel() + self.assertEqual(payroll_entry.status, "Cancelled") + + frappe.set_user("Administrator") + def test_payroll_entry_status(self): company_doc = frappe.get_doc("Company", "_Test Company") employee = make_employee("test_employee@payroll.com", company=company_doc.name) diff --git a/hrms/payroll/doctype/salary_slip/test_salary_slip.py b/hrms/payroll/doctype/salary_slip/test_salary_slip.py index f7b52c2d53..2342dfce84 100644 --- a/hrms/payroll/doctype/salary_slip/test_salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/test_salary_slip.py @@ -2230,16 +2230,6 @@ def make_earning_salary_component( if setup or test_tax: make_salary_component(data, test_tax, company_list) - data.append( - { - "salary_component": "Basic Salary", - "abbr": "BS", - "condition": "base < 10000", - "formula": "base*.2", - "type": "Earning", - "amount_based_on_formula": 1, - } - ) return data diff --git a/hrms/payroll/doctype/salary_structure/test_salary_structure.py b/hrms/payroll/doctype/salary_structure/test_salary_structure.py index 32e78a38cd..81e163528d 100644 --- a/hrms/payroll/doctype/salary_structure/test_salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/test_salary_structure.py @@ -231,7 +231,7 @@ def create_salary_structure_assignment( leave_encashment_amount_per_day=None, ): if not currency: - currency = "INR" or "INR" + currency = "INR" if not allow_duplicate and frappe.db.exists("Salary Structure Assignment", {"employee": employee}): frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""", (employee)) diff --git a/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js b/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js index 513fb9a245..4f383e6aa2 100644 --- a/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js +++ b/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js @@ -69,6 +69,22 @@ frappe.ui.form.on("Salary Structure Assignment", { ); frm.page.set_inner_btn_group_as_primary(__("Create")); + frm.add_custom_button( + __("See CTC Break-up"), + function () { + if (!frm.doc.ctc) { + frm.scroll_to_field("ctc"); + frappe.throw( + __("Please set employee's total cost to company to see CTC breakup."), + ); + } + frappe.set_route("query-report", "Employee CTC Break-up", { + employee: frm.doc.employee, + salary_structure_assignment: frm.doc.name, + }); + }, + __("Actions"), + ); frm.add_custom_button( __("Preview Salary Slip"), function () { diff --git a/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json index e07e1c6fa3..ee35e6583c 100644 --- a/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json +++ b/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json @@ -22,6 +22,7 @@ "currency", "section_break_7", "base", + "ctc", "column_break_9", "variable", "amended_from", @@ -242,11 +243,17 @@ "fieldtype": "Currency", "label": "Leave Encashment Amount Per Day", "options": "currency" + }, + { + "allow_on_submit": 1, + "fieldname": "ctc", + "fieldtype": "Currency", + "label": "Total Cost To Company (CTC)" } ], "is_submittable": 1, "links": [], - "modified": "2026-02-26 14:37:43.779340", + "modified": "2026-05-14 13:33:55.869281", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Structure Assignment", diff --git a/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py index 1abad6c98a..7f6ee935db 100644 --- a/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py +++ b/hrms/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py @@ -30,6 +30,7 @@ class SalaryStructureAssignment(Document): amended_from: DF.Link | None base: DF.Currency company: DF.Link + ctc: DF.Currency currency: DF.Link department: DF.Link | None designation: DF.Link | None diff --git a/hrms/payroll/report/employee_ctc_break_up/__init__.py b/hrms/payroll/report/employee_ctc_break_up/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.js b/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.js new file mode 100644 index 0000000000..d51d66ae27 --- /dev/null +++ b/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.js @@ -0,0 +1,59 @@ +// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Employee CTC Break-up"] = { + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + reqd: 1, + default: frappe.defaults.get_user_default("Company"), + }, + { + fieldname: "employee", + label: __("Employee"), + fieldtype: "Link", + options: "Employee", + reqd: 1, + get_query: function () { + let company = frappe.query_report.get_filter_value("company"); + return { + filters: { + company: company, + }, + }; + }, + on_change: function () { + frappe.query_report.set_filter_value("salary_structure_assignment", ""); + }, + }, + { + fieldname: "salary_structure_assignment", + label: __("Salary Structure Assignment"), + fieldtype: "Link", + options: "Salary Structure Assignment", + reqd: 1, + get_query: function () { + let employee = frappe.query_report.get_filter_value("employee"); + if (!employee) return; + return { + filters: { + employee: employee, + docstatus: 1, + }, + }; + }, + }, + ], + formatter: function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (data?.bold && value) value = `${value}`; + if (column.fieldname == "type" && value) { + let indicator_color = value === "Fixed" ? "blue" : "orange"; + value = `${value}`; + } + return value; + }, +}; diff --git a/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.json b/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.json new file mode 100644 index 0000000000..ba2cfadd01 --- /dev/null +++ b/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "add_translate_data": 0, + "columns": [], + "creation": "2026-04-15 23:59:12.988472", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2026-05-14 12:08:50.414476", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Employee CTC Break-up", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Salary Structure Assignment", + "report_name": "Employee CTC Break-up", + "report_type": "Script Report", + "roles": [ + { + "role": "HR Manager" + }, + { + "role": "System Manager" + }, + { + "role": "HR User" + } + ], + "timeout": 0 +} diff --git a/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py b/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py new file mode 100644 index 0000000000..103ac1e2fc --- /dev/null +++ b/hrms/payroll/report/employee_ctc_break_up/employee_ctc_break_up.py @@ -0,0 +1,315 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import flt, get_link_to_form +from frappe.utils.formatters import format_value +from frappe.utils.jinja import render_template + +from hrms.payroll.doctype.salary_structure.salary_structure import make_salary_slip + + +class SalaryBreakupReport: + def __init__(self, employee, salary_structure_assignment): + self.employee = employee + self.salary_structure_assignment = salary_structure_assignment + + self.salary_structure, self.currency, self.assignment_date, self.income_tax_slab, self.ctc = ( + frappe.get_value( + "Salary Structure Assignment", + {"name": salary_structure_assignment, "employee": employee}, + ["salary_structure", "currency", "from_date", "income_tax_slab", "ctc"], + ) + ) + self.validate_ctc() + self.salary_slip = make_salary_slip( + self.salary_structure, + employee=self.employee, + for_preview=1, + as_print=False, + posting_date=frappe.flags.posting_date if frappe.flags.in_test else None, + ) + self.net_pay = self.salary_slip.net_pay + self.gross_pay = self.salary_slip.gross_pay + self.payroll_frequency = self.salary_slip.payroll_frequency + self.cycle_multiplier = { + "Monthly": 12, + "Fortnightly": 24, + "Bimonthly": 6, + "Weekly": 52, + "Daily": 365, + }.get(self.payroll_frequency) + self.salary_components = [] + self.earning_components = [] + self.deduction_components = [] + self.tax_components = [] + self.total_net_earnings = [] + self.total_gross_earnings = [] + + def validate_ctc(self): + if not self.ctc: + frappe.throw( + _("Please set cost to company(CTC) for employee {0} in the {1}").format( + frappe.bold(self.employee), + get_link_to_form( + "Salary Structure Assignment", + self.salary_structure_assignment, + "Salary Structure Assignment", + ), + ), + title=_("CTC Missing for Employee"), + ) + + def get_data(self): + self.set_salary_component_details() + self.calculate_yearly_amounts_and_percent_of_ctc() + self.indent_salary_components() + self.separate_salary_components_by_type() + self.set_type_and_formula() + self.set_totals_row_for_component_types() + self.set_net_and_gross_earning_rows() + + return ( + self.earning_components + + self.deduction_components + + self.tax_components + + self.total_net_earnings + + self.total_gross_earnings + ) + + def set_salary_component_details(self): + salary_component_details = frappe.db.get_all( + "Salary Detail", + filters={"parent": self.salary_structure}, + fields=["salary_component", "amount_based_on_formula", "formula"], + ) + + self.salary_components = [ + { + "salary_component": component.salary_component, + "per_cycle": component.amount, + "abbr": component.abbr, + "is_tax_component": component.variable_based_on_taxable_salary, + "component_type": component.parentfield, + } + for component in self.salary_slip.earnings + self.salary_slip.deductions + ] + + for component in self.salary_components: + component_details = next( + ( + detail + for detail in salary_component_details + if component.get("salary_component") == detail.salary_component + ), + {}, + ) + component.update(component_details) + + def calculate_yearly_amounts_and_percent_of_ctc(self): + for component in self.salary_components: + annual_amount = component.get("per_cycle", 0) * self.cycle_multiplier + component.update( + { + "annual": flt(annual_amount, 2), + "percent_of_ctc": self.calculate_percent_of_ctc(annual_amount), + } + ) + + def separate_salary_components_by_type(self): + self.earning_components = [ + component for component in self.salary_components if component.get("component_type") == "earnings" + ] + self.deduction_components = [ + component + for component in self.salary_components + if component.get("component_type") == "deductions" and not component.get("is_tax_component") + ] + self.tax_components = [ + component for component in self.salary_components if component.get("is_tax_component") + ] + + def set_type_and_formula(self): + for component in self.earning_components + self.deduction_components: + component["type"] = "Formula" if component.get("amount_based_on_formula") else "Fixed" + component["formula"] = ( + component.get("formula") or "-" + if component.get("amount_based_on_formula") + else component.get("per_cycle") or "-" + ) + + def set_totals_row_for_component_types(self): + def calculate_total(period, components): + total = 0 + for component in components: + total += component.get(period) + return total + + def set_totals_row(component_type): + components = { + "Earnings": self.earning_components, + "Deductions": self.deduction_components, + "Tax Deductions": self.tax_components, + }.get(component_type) + totals_row = { + "salary_component": component_type, + "type": "", + "formula": "", + "bold": True, + "per_cycle": calculate_total("per_cycle", components), + "annual": calculate_total("annual", components), + "percent_of_ctc": self.calculate_percent_of_ctc(calculate_total("annual", components)), + } + components.insert(0, totals_row) + + for component_type in ("Earnings", "Deductions", "Tax Deductions"): + set_totals_row(component_type) + + def set_net_and_gross_earning_rows(self): + self.total_net_earnings = [ + { + "salary_component": "Total Net Earnings", + "type": "", + "formula": "", + "per_cycle": self.net_pay, + "annual": self.net_pay * self.cycle_multiplier, + "percent_of_ctc": self.calculate_percent_of_ctc(self.net_pay * self.cycle_multiplier), + "bold": True, + } + ] + self.total_gross_earnings = [ + { + "salary_component": "Total Gross Earnings", + "type": "", + "formula": "", + "per_cycle": self.gross_pay, + "annual": self.gross_pay * self.cycle_multiplier, + "percent_of_ctc": self.calculate_percent_of_ctc(self.gross_pay * self.cycle_multiplier), + "bold": True, + } + ] + + def calculate_percent_of_ctc(self, amount): + return flt(amount * 100 / self.ctc, 2) + + def get_per_cycle_ctc(self): + return flt(self.ctc / self.cycle_multiplier, 2) + + def indent_salary_components(self): + for component in self.salary_components: + component["indent"] = 1 + + def format_currency(self, amount): + return format_value(amount, currency=self.currency) + + def get_columns(self) -> list[dict]: + """Return columns for the report. + + One field definition per column, just like a DocType field definition. + """ + return [ + { + "label": _("Component"), + "fieldname": "salary_component", + "fieldtype": "Data", + "width": 300, + "fixed": 1, + }, + { + "label": _("Type"), + "fieldname": "type", + "fieldtype": "Data", + "width": 150, + "fixed": 1, + }, + { + "label": _("Formula/Amount"), + "fieldname": "formula", + "fieldtype": "Data", + "width": 250, + "fixed": 1, + }, + { + "label": _(self.payroll_frequency), + "fieldname": "per_cycle", + "fieldtype": "Currency", + "width": 250, + "options": "currency", + "fixed": 1, + }, + { + "label": _("Annual"), + "fieldname": "annual", + "fieldtype": "Currency", + "width": 250, + "options": "currency", + "fixed": 1, + }, + { + "label": _("Percent of CTC (%)"), + "fieldname": "percent_of_ctc", + "fieldtype": "Percent", + "width": 200, + "fixed": 1, + }, + { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "hidden": 1, + "value": self.currency, + }, + ] + + def get_message(self): + path = "hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html" + context = dict( + { + "employee_name": frappe.get_value("Employee", self.employee, "employee_name"), + "designation": frappe.get_value("Employee", self.employee, "designation"), + "salary_structure": self.salary_structure, + "per_cycle": self.payroll_frequency, + "annual_ctc": self.format_currency(self.ctc), + "per_cycle_ctc": self.format_currency(self.get_per_cycle_ctc()), + "per_cycle_gross_pay": self.format_currency(self.gross_pay), + "per_cycle_net_pay": self.format_currency(self.net_pay), + "assignment_date": frappe.utils.global_date_format(self.assignment_date, "long"), + "income_tax_slab": self.income_tax_slab, + } + ) + # nosemgrep: frappe-semgrep-rules.rules.security.frappe-ssti + employee_profile_card = render_template(path, context=context, is_path=True) + return employee_profile_card + + +def execute(filters: dict | None = None): + """Return columns and data for the report. + + This is the main entry point for the report. It accepts the filters as a + dictionary and should return columns and data. It is called by the framework + every time the report is refreshed or a filter is updated. + """ + employee = filters.get("employee") + salary_structure_assignment = filters.get("salary_structure_assignment") + + missing_filter = ( + "Employee" + if not employee + else "Salary Structure Assignment" + if not salary_structure_assignment + else None + ) + if missing_filter: + frappe.throw( + _("Please set {0} to get CTC report").format(frappe.bold(missing_filter)), + title=_("Missing value for filters"), + ) + + salary_breakup_report = SalaryBreakupReport(employee, salary_structure_assignment) + + data = salary_breakup_report.get_data() + columns = salary_breakup_report.get_columns() + message = salary_breakup_report.get_message() + return columns, data, message, None, None diff --git a/hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html b/hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html new file mode 100644 index 0000000000..cd6d51a1cb --- /dev/null +++ b/hrms/payroll/report/employee_ctc_break_up/employee_profile_card.html @@ -0,0 +1,167 @@ + + +
+ +
+
+ {% if image %} + + {% else %} +
{{frappe.utils.get_abbr(employee_name)}}
+ {% endif %} +
+ +
+ {{ employee_name }} + {% if designation %} + · + {{designation}} + {% endif %} +
+
{{_("Salary Structure: ") + salary_structure}}
+ {% if income_tax_slab %}
{{_("Income Tax Slab: ") + income_tax_slab}}
{% endif %} +
{{ _("Assignment Date: ") + assignment_date}}
+
+
+
+ +
+ +
+
+
+ {{ annual_ctc }} +
+
+ {{_("Annual CTC")}} +
+
+ +
+
+ {{ per_cycle_ctc }} +
+ +
+ {{_(per_cycle + " CTC")}} +
+
+
+
+
+
+ {{ per_cycle_gross_pay }} +
+ +
+ {{_(per_cycle +" Gross Pay")}} +
+
+ +
+
+ {{ per_cycle_net_pay }} +
+ +
+ {{_(per_cycle + " Net Pay")}} +
+
+ +
+ +
+
diff --git a/hrms/payroll/report/employee_ctc_break_up/test_employee_ctc_breakup.py b/hrms/payroll/report/employee_ctc_break_up/test_employee_ctc_breakup.py new file mode 100644 index 0000000000..7ee2a69fb6 --- /dev/null +++ b/hrms/payroll/report/employee_ctc_break_up/test_employee_ctc_breakup.py @@ -0,0 +1,254 @@ +import frappe +from frappe.utils import add_months, flt, get_year_start, getdate + +from erpnext.setup.doctype.employee.test_employee import make_employee + +from hrms.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import ( + create_payroll_period, +) +from hrms.payroll.doctype.salary_structure.test_salary_structure import ( + create_salary_structure_assignment, + make_salary_structure, +) +from hrms.payroll.report.employee_ctc_break_up.employee_ctc_break_up import SalaryBreakupReport +from hrms.tests.utils import HRMSTestSuite + + +class TestEmployeeCTCBreakup(HRMSTestSuite): + """ + `make_salary_structure` has the following setup + Earning Components: + - Basic (base set in salary structure assignment) + - HRA (fixed 3000) + - Special Allowance (base/2) + Deduction Components: + - Professional Tax (fixed 200) + Income Tax Details (only applicable if test_tax is set to True in salary structure): + - Slabs + - 0 to 250000: 0% + - 250000 to 500000: 5% if annual taxable income exceeds 50000 else 0% + - 500000 to 1000000: 20% + - Above 1000000: 30% + - Tax exemption of 50000 + - 4% cess + """ + + def test_ctc_summary_cards(self): + test_data = { + "Monthly": { + # if + "ctc": 1116000, + "base": 60000, + "currency": "INR", + # test + "annual_ctc": "₹ 1,116,000.00", + "per_cycle_ctc": 93000, + "gross_pay": 93000, + "net_pay": 92800, # gross pay - professional tax + }, + "Fortnightly": { + # if + "ctc": 648000, + "base": 16000, + "currency": "USD", + # test + "annual_ctc": "$ 648,000.00", + "per_cycle_ctc": 27000, + "gross_pay": 27000, + "net_pay": 26800, # gross pay - professional tax + }, + } + + for payroll_frequency, data in test_data.items(): + employee = make_employee(f"test_ctc{payroll_frequency}@example.com", company="_Test Company") + salary_structure = make_salary_structure( + "Test CTC Breakup", payroll_frequency=payroll_frequency, currency=data.get("currency") + ) + salary_structure_assignment = create_salary_structure_assignment( + employee, salary_structure.name, base=data.get("base"), currency=data.get("currency") + ) + salary_structure_assignment.ctc = data.get("ctc") + salary_structure_assignment.save() + + ctc_breakup = SalaryBreakupReport(employee, salary_structure_assignment.name) + + self.assertEqual(ctc_breakup.format_currency(ctc_breakup.ctc), data["annual_ctc"]) + self.assertEqual(ctc_breakup.get_per_cycle_ctc(), data["per_cycle_ctc"]) + self.assertEqual(ctc_breakup.gross_pay, data["gross_pay"]) + self.assertEqual(ctc_breakup.net_pay, data["net_pay"]) + + def test_income_tax_net_pay_calculation_at_the_start_of_payroll_period(self): + employee = make_employee("test_ctc@example.com", company="_Test Company") + salary_structure = make_salary_structure( + "Test CTC Breakup with Tax", payroll_frequency="Monthly", currency="INR", test_tax=True + ) + salary_structure_assignment = create_salary_structure_assignment( + employee, salary_structure.name, base=60000, currency="INR", from_date=get_year_start(getdate()) + ) + salary_structure_assignment.ctc = 1116000 + salary_structure_assignment.save() + + """With the given setup, the annual tax will be, + taxable_income = 1116000 (total earnings) - 2400 (Professional Tax) - 50000 (Tax Exemption) = 1063600 + Tax amount = 12500 (5% of 250000) + 100000 (20% of 500000) + 19080(30% of 63600) = 131580 + Cess = 4% of tax = 5263.2 + Total tax amount = 136843.2 + Monthly tax = flt(136843.25 / 12) = 11404 + Monthly net pay = Gross pay - Professional Tax - Monthly tax = 93000 - 200 - 11404 = 81396 + """ + monthly_income_tax = 11404 + annual_tax = flt(monthly_income_tax * 12, 2) + frappe.flags.posting_date = get_year_start(getdate()) + ctc_breakup = SalaryBreakupReport(employee, salary_structure_assignment.name) + self.assertEqual(ctc_breakup.net_pay, 81396) + income_tax_row = ctc_breakup.get_data()[-3] + self.assertEqual(income_tax_row.get("per_cycle"), monthly_income_tax) + self.assertEqual(income_tax_row.get("annual"), annual_tax) + + def test_income_tax_net_pay_calculation_at_the_end_of_payroll_period(self): + employee = make_employee("test_ctc@example.com", company="_Test Company") + salary_structure = make_salary_structure( + "Test CTC Breakup with Tax", payroll_frequency="Monthly", currency="INR", test_tax=True + ) + salary_structure_assignment = create_salary_structure_assignment( + employee, salary_structure.name, base=60000, currency="INR", from_date=get_year_start(getdate()) + ) + salary_structure_assignment.ctc = 1116000 + salary_structure_assignment.save() + + """With the given setup, the annual tax will be for last two months, + taxable_income = 186000 (total earnings) - 400 (Professional Tax) - 50000 (Tax Exemption) = 135600 + Tax amount = 0 (taxable earning < 250000) + Monthly net pay = Gross pay - Professional Tax - Monthly tax = 93000 - 200 - 11404 = 81396 + """ + monthly_income_tax = annual_tax = 0 + + frappe.flags.posting_date = add_months(get_year_start(getdate()), 10) + ctc_breakup = SalaryBreakupReport(employee, salary_structure_assignment.name) + self.assertEqual(ctc_breakup.net_pay, 92800) + income_tax_row = ctc_breakup.get_data()[-3] + self.assertEqual(income_tax_row.get("per_cycle"), monthly_income_tax) + self.assertEqual(income_tax_row.get("annual"), annual_tax) + + def test_report(self): + employee = make_employee("test_ctc@example.com", company="_Test Company") + salary_structure = make_salary_structure( + "Test CTC Breakup with Tax", payroll_frequency="Monthly", currency="INR", test_tax=True + ) + salary_structure_assignment = create_salary_structure_assignment( + employee, salary_structure.name, base=60000, currency="INR", from_date=get_year_start(getdate()) + ) + salary_structure_assignment.ctc = 1116000 + salary_structure_assignment.save() + frappe.flags.posting_date = get_year_start(getdate()) + ctc_breakup = SalaryBreakupReport(employee, salary_structure_assignment.name) + data = ctc_breakup.get_data() + self.assertEqual(len(data), 11) + + earning_components = ctc_breakup.earning_components + deduction_components = ctc_breakup.deduction_components + tax_components = ctc_breakup.tax_components + net_pay_row = ctc_breakup.total_net_earnings + gross_pay_row = ctc_breakup.total_gross_earnings + + earning_components_totals = earning_components[0] + self.assertEqual(earning_components_totals.get("salary_component"), "Earnings") + self.assertEqual(earning_components_totals.get("type"), "") + self.assertEqual(earning_components_totals.get("formula"), "") + self.assertEqual(earning_components_totals.get("per_cycle"), 93000) + self.assertEqual(earning_components_totals.get("annual"), 1116000) + self.assertEqual(earning_components_totals.get("percent_of_ctc"), 100) + + basic_earning_component = earning_components[1] + self.assertEqual(basic_earning_component.get("salary_component"), "Basic Salary") + self.assertEqual(basic_earning_component.get("type"), "Formula") + self.assertEqual(basic_earning_component.get("formula"), "base") + self.assertEqual(basic_earning_component.get("per_cycle"), 60000) + self.assertEqual(basic_earning_component.get("annual"), 720000) + self.assertEqual(basic_earning_component.get("percent_of_ctc"), 64.52) + + hra_earning_component = earning_components[2] + self.assertEqual(hra_earning_component.get("salary_component"), "HRA") + self.assertEqual(hra_earning_component.get("type"), "Fixed") + self.assertEqual(hra_earning_component.get("formula"), 3000) + self.assertEqual(hra_earning_component.get("per_cycle"), 3000) + self.assertEqual(hra_earning_component.get("annual"), 36000) + self.assertEqual(hra_earning_component.get("percent_of_ctc"), 3.23) + + special_allowance_earning_component = earning_components[3] + self.assertEqual(special_allowance_earning_component.get("salary_component"), "Special Allowance") + self.assertEqual(special_allowance_earning_component.get("type"), "Formula") + self.assertEqual(special_allowance_earning_component.get("formula"), "BS\n*.5") + self.assertEqual(special_allowance_earning_component.get("per_cycle"), 30000) + self.assertEqual(special_allowance_earning_component.get("annual"), 360000) + self.assertEqual(special_allowance_earning_component.get("percent_of_ctc"), 32.26) + + deduction_components_totals = deduction_components[0] + self.assertEqual(deduction_components_totals.get("salary_component"), "Deductions") + self.assertEqual(deduction_components_totals.get("type"), "") + self.assertEqual(deduction_components_totals.get("formula"), "") + self.assertEqual(deduction_components_totals.get("per_cycle"), 200) + self.assertEqual(deduction_components_totals.get("annual"), 2400) + self.assertEqual(deduction_components_totals.get("percent_of_ctc"), 0.22) + + professional_tax_deduction_component = deduction_components[1] + self.assertEqual(professional_tax_deduction_component.get("salary_component"), "Professional Tax") + self.assertEqual(professional_tax_deduction_component.get("type"), "Fixed") + self.assertEqual(professional_tax_deduction_component.get("formula"), 200) + self.assertEqual(professional_tax_deduction_component.get("per_cycle"), 200) + self.assertEqual(professional_tax_deduction_component.get("annual"), 2400) + self.assertEqual(professional_tax_deduction_component.get("percent_of_ctc"), 0.22) + + tax_components_totals = tax_components[0] + self.assertEqual(tax_components_totals.get("salary_component"), "Tax Deductions") + self.assertEqual(tax_components_totals.get("type"), "") + self.assertEqual(tax_components_totals.get("formula"), "") + self.assertEqual(tax_components_totals.get("per_cycle"), 11404) + self.assertEqual(tax_components_totals.get("annual"), 136848) + self.assertEqual(tax_components_totals.get("percent_of_ctc"), 12.26) + + self.assertEqual(net_pay_row[0].get("salary_component"), "Total Net Earnings") + self.assertEqual(net_pay_row[0].get("per_cycle"), 81396) + self.assertEqual(net_pay_row[0].get("annual"), 976752) + self.assertEqual(net_pay_row[0].get("percent_of_ctc"), 87.52) + + self.assertEqual(gross_pay_row[0].get("salary_component"), "Total Gross Earnings") + self.assertEqual(gross_pay_row[0].get("per_cycle"), 93000) + self.assertEqual(gross_pay_row[0].get("annual"), 1116000) + self.assertEqual(gross_pay_row[0].get("percent_of_ctc"), 100) + + self.assertEqual( + flt( + basic_earning_component.get("percent_of_ctc") + + hra_earning_component.get("percent_of_ctc") + + special_allowance_earning_component.get("percent_of_ctc"), + 0, + ), + earning_components_totals.get("percent_of_ctc"), + ) + self.assertEqual( + professional_tax_deduction_component.get("percent_of_ctc"), + deduction_components_totals.get("percent_of_ctc"), + ) + self.assertEqual( + net_pay_row[0].get("percent_of_ctc") + + tax_components_totals.get("percent_of_ctc") + + deduction_components_totals.get("percent_of_ctc"), + earning_components_totals.get("percent_of_ctc"), + ) + + def test_ctc_validation(self): + employee = make_employee("test_ctc@example.com", company="_Test Company") + salary_structure = make_salary_structure( + "Test CTC Breakup with Tax", payroll_frequency="Monthly", currency="INR", test_tax=True + ) + salary_structure_assignment = create_salary_structure_assignment( + employee, salary_structure.name, base=60000, currency="INR", from_date=get_year_start(getdate()) + ) + self.assertRaises( + frappe.ValidationError, SalaryBreakupReport, employee, salary_structure_assignment.name + ) + salary_structure_assignment.ctc = 1116000 + salary_structure_assignment.save() + ctc_breakup = SalaryBreakupReport(employee, salary_structure_assignment.name) + self.assertEqual(ctc_breakup.ctc, 1116000) diff --git a/hrms/workspace_sidebar/payroll.json b/hrms/workspace_sidebar/payroll.json index 3501a13da9..00e691e371 100644 --- a/hrms/workspace_sidebar/payroll.json +++ b/hrms/workspace_sidebar/payroll.json @@ -15,6 +15,7 @@ "label": "Home", "link_to": "Payroll", "link_type": "Workspace", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -27,6 +28,7 @@ "label": "Dashboard", "link_to": "Payroll", "link_type": "Dashboard", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -39,6 +41,7 @@ "label": "Payroll Entry", "link_to": "Payroll Entry", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -51,6 +54,7 @@ "label": "Salary Structure Assignment", "link_to": "Salary Structure Assignment", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -63,6 +67,7 @@ "label": "Salary Slip", "link_to": "Salary Slip", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -75,6 +80,7 @@ "label": "Additional Salary", "link_to": "Additional Salary", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -87,6 +93,7 @@ "label": "Salary Withholding", "link_to": "Salary Withholding", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -98,9 +105,22 @@ "keep_closed": 1, "label": "Reports", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Section Break" }, + { + "child": 1, + "collapsible": 1, + "indent": 0, + "keep_closed": 0, + "label": "Employee CTC Break-up", + "link_to": "Employee CTC Break-up", + "link_type": "Report", + "open_in_new_tab": 1, + "show_arrow": 0, + "type": "Link" + }, { "child": 1, "collapsible": 1, @@ -110,6 +130,7 @@ "label": "Salary Register", "link_to": "Salary Register", "link_type": "Report", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -122,6 +143,7 @@ "label": "Income Tax Deductions", "link_to": "Income Tax Deductions", "link_type": "Report", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -134,6 +156,7 @@ "label": "Professional Tax Deductions", "link_to": "Professional Tax Deductions", "link_type": "Report", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -146,6 +169,7 @@ "label": "General Ledger", "link_to": "General Ledger", "link_type": "Report", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -158,6 +182,7 @@ "label": "Accounts Payable", "link_to": "Accounts Payable", "link_type": "Report", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -169,6 +194,7 @@ "keep_closed": 1, "label": "Setup", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Section Break" }, @@ -181,6 +207,7 @@ "label": "Salary Component", "link_to": "Salary Component", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -193,6 +220,7 @@ "label": "Salary Structure", "link_to": "Salary Structure", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" }, @@ -205,11 +233,12 @@ "label": "Settings", "link_to": "Payroll Settings", "link_type": "DocType", + "open_in_new_tab": 1, "show_arrow": 0, "type": "Link" } ], - "modified": "2026-01-08 14:16:38.399025", + "modified": "2026-05-14 12:18:07.894971", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll",