Full Code of frappe/hrms for AI

develop fdb15fb88188 cached
1451 files
17.2 MB
4.6M tokens
3107 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (18,354K chars total). Download the full file to get everything.
Repository: frappe/hrms
Branch: develop
Commit: fdb15fb88188
Files: 1451
Total size: 17.2 MB

Directory structure:
gitextract_bjjbcr75/

├── .editorconfig
├── .git-blame-ignore-revs
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   ├── config.yml
│   │   └── feature_request.yaml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── helper/
│   │   ├── apps.json
│   │   ├── documentation.py
│   │   ├── install.sh
│   │   ├── site_config.json
│   │   ├── translation.py
│   │   └── update_pot_file.sh
│   ├── labeler.yml
│   ├── release.yml
│   └── workflows/
│       ├── build_image.yml
│       ├── ci.yml
│       ├── docs_checker.yml
│       ├── generate-pot-file.yml
│       ├── initiate_release.yml
│       ├── labeller.yml
│       ├── linters.yml
│       ├── on_release.yml
│       ├── release_notes.yml
│       ├── run-individual-tests.yml
│       └── stale.yml
├── .gitignore
├── .gitmodules
├── .mergify.yml
├── .pre-commit-config.yaml
├── .releaserc
├── .semgrepignore
├── CODE_OF_CONDUCT.md
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── codecov.yml
├── commitlint.config.js
├── crowdin.yml
├── docker/
│   ├── docker-compose.yml
│   └── init.sh
├── frontend/
│   ├── .eslintrc.js
│   ├── .gitignore
│   ├── .prettierrc.json
│   ├── index.html
│   ├── ionic.config.json
│   ├── jsconfig.json
│   ├── package.json
│   ├── postcss.config.js
│   ├── public/
│   │   ├── frappe-push-notification.js
│   │   └── sw.js
│   ├── src/
│   │   ├── App.vue
│   │   ├── components/
│   │   │   ├── AttendanceCalendar.vue
│   │   │   ├── AttendanceRequestItem.vue
│   │   │   ├── BaseLayout.vue
│   │   │   ├── BottomTabs.vue
│   │   │   ├── CheckInPanel.vue
│   │   │   ├── CustomIonModal.vue
│   │   │   ├── EmployeeAdvanceBalance.vue
│   │   │   ├── EmployeeAdvanceItem.vue
│   │   │   ├── EmployeeAvatar.vue
│   │   │   ├── EmployeeCheckinItem.vue
│   │   │   ├── EmptyState.vue
│   │   │   ├── ExpenseAdvancesTable.vue
│   │   │   ├── ExpenseClaimItem.vue
│   │   │   ├── ExpenseClaimSummary.vue
│   │   │   ├── ExpenseItems.vue
│   │   │   ├── ExpenseTaxesTable.vue
│   │   │   ├── ExpensesTable.vue
│   │   │   ├── FilePreviewModal.vue
│   │   │   ├── FileUploaderView.vue
│   │   │   ├── FormField.vue
│   │   │   ├── FormView.vue
│   │   │   ├── FormattedField.vue
│   │   │   ├── Holidays.vue
│   │   │   ├── InstallPrompt.vue
│   │   │   ├── LeaveBalance.vue
│   │   │   ├── LeaveRequestItem.vue
│   │   │   ├── Link.vue
│   │   │   ├── ListFiltersActionSheet.vue
│   │   │   ├── ListItem.vue
│   │   │   ├── ListView.vue
│   │   │   ├── ProfileInfoModal.vue
│   │   │   ├── QuickLinks.vue
│   │   │   ├── RequestActionSheet.vue
│   │   │   ├── RequestList.vue
│   │   │   ├── RequestPanel.vue
│   │   │   ├── SalaryDetailTable.vue
│   │   │   ├── SalarySlipItem.vue
│   │   │   ├── SemicircleChart.vue
│   │   │   ├── ShiftAssignmentItem.vue
│   │   │   ├── ShiftRequestItem.vue
│   │   │   ├── TabButtons.vue
│   │   │   ├── WorkflowActionSheet.vue
│   │   │   └── icons/
│   │   │       ├── AttendanceIcon.vue
│   │   │       ├── EmployeeAdvanceIcon.vue
│   │   │       ├── ExpenseIcon.vue
│   │   │       ├── FrappeHRLogo.vue
│   │   │       ├── FrappeHRLogoType.vue
│   │   │       ├── HomeIcon.vue
│   │   │       ├── LeaveIcon.vue
│   │   │       ├── SalaryIcon.vue
│   │   │       └── ShiftIcon.vue
│   │   ├── composables/
│   │   │   ├── index.js
│   │   │   ├── realtime.js
│   │   │   └── workflow.js
│   │   ├── data/
│   │   │   ├── advances.js
│   │   │   ├── attendance.js
│   │   │   ├── claims.js
│   │   │   ├── config/
│   │   │   │   └── requestSummaryFields.js
│   │   │   ├── currencies.js
│   │   │   ├── employee.js
│   │   │   ├── employees.js
│   │   │   ├── leaves.js
│   │   │   ├── notifications.js
│   │   │   ├── session.js
│   │   │   └── user.js
│   │   ├── main.css
│   │   ├── main.js
│   │   ├── plugins/
│   │   │   └── translationsPlugin.js
│   │   ├── router/
│   │   │   ├── advances.js
│   │   │   ├── attendance.js
│   │   │   ├── claims.js
│   │   │   ├── index.js
│   │   │   ├── leaves.js
│   │   │   └── salary_slips.js
│   │   ├── socket.js
│   │   ├── theme/
│   │   │   └── variables.css
│   │   ├── utils/
│   │   │   ├── commonUtils.js
│   │   │   ├── dayjs.js
│   │   │   ├── dialogs.js
│   │   │   ├── formatters.js
│   │   │   ├── ionicConfig.js
│   │   │   └── pushNotifications.js
│   │   └── views/
│   │       ├── AppSettings.vue
│   │       ├── Home.vue
│   │       ├── InvalidEmployee.vue
│   │       ├── Login.vue
│   │       ├── Notifications.vue
│   │       ├── Profile.vue
│   │       ├── TabbedView.vue
│   │       ├── attendance/
│   │       │   ├── AttendanceRequestForm.vue
│   │       │   ├── AttendanceRequestList.vue
│   │       │   ├── Dashboard.vue
│   │       │   ├── EmployeeCheckinList.vue
│   │       │   ├── ShiftAssignmentForm.vue
│   │       │   ├── ShiftAssignmentList.vue
│   │       │   ├── ShiftRequestForm.vue
│   │       │   └── ShiftRequestList.vue
│   │       ├── employee_advance/
│   │       │   ├── Form.vue
│   │       │   └── List.vue
│   │       ├── expense_claim/
│   │       │   ├── Dashboard.vue
│   │       │   ├── Form.vue
│   │       │   └── List.vue
│   │       ├── leave/
│   │       │   ├── Dashboard.vue
│   │       │   ├── Form.vue
│   │       │   └── List.vue
│   │       └── salary_slip/
│   │           ├── Dashboard.vue
│   │           └── Detail.vue
│   ├── tailwind.config.js
│   └── vite.config.js
├── hrms/
│   ├── __init__.py
│   ├── api/
│   │   ├── __init__.py
│   │   ├── oauth.py
│   │   ├── roster.py
│   │   └── system_settings.py
│   ├── config/
│   │   ├── __init__.py
│   │   ├── desktop.py
│   │   └── docs.py
│   ├── controllers/
│   │   ├── employee_boarding_controller.py
│   │   ├── employee_reminders.py
│   │   └── tests/
│   │       └── test_employee_reminders.py
│   ├── desktop_icon/
│   │   ├── expenses.json
│   │   ├── frappe_hr.json
│   │   ├── leaves.json
│   │   ├── payroll.json
│   │   ├── people.json
│   │   ├── performance.json
│   │   ├── recruitment.json
│   │   ├── shift_&_attendance.json
│   │   ├── tax_&_benefits.json
│   │   └── tenure.json
│   ├── hooks.py
│   ├── hr/
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── dashboard_chart/
│   │   │   ├── appraisal_overview/
│   │   │   │   └── appraisal_overview.json
│   │   │   ├── attendance_count/
│   │   │   │   └── attendance_count.json
│   │   │   ├── claims_by_type/
│   │   │   │   └── claims_by_type.json
│   │   │   ├── department_wise_employee_count/
│   │   │   │   └── department_wise_employee_count.json
│   │   │   ├── department_wise_expense_claims/
│   │   │   │   └── department_wise_expense_claims.json
│   │   │   ├── department_wise_openings/
│   │   │   │   └── department_wise_openings.json
│   │   │   ├── department_wise_timesheet_hours/
│   │   │   │   └── department_wise_timesheet_hours.json
│   │   │   ├── designation_wise_employee_count/
│   │   │   │   └── designation_wise_employee_count.json
│   │   │   ├── designation_wise_openings/
│   │   │   │   └── designation_wise_openings.json
│   │   │   ├── employee_advance_status/
│   │   │   │   └── employee_advance_status.json
│   │   │   ├── employees_by_age/
│   │   │   │   └── employees_by_age.json
│   │   │   ├── employees_by_branch/
│   │   │   │   └── employees_by_branch.json
│   │   │   ├── employees_by_grade/
│   │   │   │   └── employees_by_grade.json
│   │   │   ├── employees_by_type/
│   │   │   │   └── employees_by_type.json
│   │   │   ├── expense_claims/
│   │   │   │   └── expense_claims.json
│   │   │   ├── gender_diversity_ratio/
│   │   │   │   └── gender_diversity_ratio.json
│   │   │   ├── grievance_type/
│   │   │   │   └── grievance_type.json
│   │   │   ├── hiring_vs_attrition_count/
│   │   │   │   └── hiring_vs_attrition_count.json
│   │   │   ├── interview_status/
│   │   │   │   └── interview_status.json
│   │   │   ├── job_applicant_pipeline/
│   │   │   │   └── job_applicant_pipeline.json
│   │   │   ├── job_applicant_source/
│   │   │   │   └── job_applicant_source.json
│   │   │   ├── job_applicants_by_country/
│   │   │   │   └── job_applicants_by_country.json
│   │   │   ├── job_application_frequency/
│   │   │   │   └── job_application_frequency.json
│   │   │   ├── job_application_status/
│   │   │   │   └── job_application_status.json
│   │   │   ├── job_offer_status/
│   │   │   │   └── job_offer_status.json
│   │   │   ├── shift_assignment_breakup/
│   │   │   │   └── shift_assignment_breakup.json
│   │   │   ├── timesheet_activity_breakup/
│   │   │   │   └── timesheet_activity_breakup.json
│   │   │   ├── training_type/
│   │   │   │   └── training_type.json
│   │   │   ├── y_o_y_promotions/
│   │   │   │   └── y_o_y_promotions.json
│   │   │   └── y_o_y_transfers/
│   │   │       └── y_o_y_transfers.json
│   │   ├── dashboard_chart_source/
│   │   │   ├── __init__.py
│   │   │   ├── employees_by_age/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employees_by_age.js
│   │   │   │   ├── employees_by_age.json
│   │   │   │   └── employees_by_age.py
│   │   │   └── hiring_vs_attrition_count/
│   │   │       ├── __init__.py
│   │   │       ├── hiring_vs_attrition_count.js
│   │   │       ├── hiring_vs_attrition_count.json
│   │   │       └── hiring_vs_attrition_count.py
│   │   ├── doctype/
│   │   │   ├── __init__.py
│   │   │   ├── appointment_letter/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appointment_letter.js
│   │   │   │   ├── appointment_letter.json
│   │   │   │   ├── appointment_letter.py
│   │   │   │   └── test_appointment_letter.py
│   │   │   ├── appointment_letter_content/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appointment_letter_content.json
│   │   │   │   └── appointment_letter_content.py
│   │   │   ├── appointment_letter_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appointment_letter_template.js
│   │   │   │   ├── appointment_letter_template.json
│   │   │   │   ├── appointment_letter_template.py
│   │   │   │   └── test_appointment_letter_template.py
│   │   │   ├── appraisal/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal.js
│   │   │   │   ├── appraisal.json
│   │   │   │   ├── appraisal.py
│   │   │   │   └── test_appraisal.py
│   │   │   ├── appraisal_cycle/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_cycle.js
│   │   │   │   ├── appraisal_cycle.json
│   │   │   │   ├── appraisal_cycle.py
│   │   │   │   └── test_appraisal_cycle.py
│   │   │   ├── appraisal_goal/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_goal.json
│   │   │   │   └── appraisal_goal.py
│   │   │   ├── appraisal_kra/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_kra.json
│   │   │   │   └── appraisal_kra.py
│   │   │   ├── appraisal_template/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_template.js
│   │   │   │   ├── appraisal_template.json
│   │   │   │   ├── appraisal_template.py
│   │   │   │   └── test_appraisal_template.py
│   │   │   ├── appraisal_template_goal/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_template_goal.json
│   │   │   │   └── appraisal_template_goal.py
│   │   │   ├── appraisee/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisee.json
│   │   │   │   └── appraisee.py
│   │   │   ├── attendance/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── attendance.js
│   │   │   │   ├── attendance.json
│   │   │   │   ├── attendance.py
│   │   │   │   ├── attendance_calendar.js
│   │   │   │   ├── attendance_dashboard.py
│   │   │   │   ├── attendance_list.js
│   │   │   │   └── test_attendance.py
│   │   │   ├── attendance_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── attendance_request.js
│   │   │   │   ├── attendance_request.json
│   │   │   │   ├── attendance_request.py
│   │   │   │   ├── attendance_request_dashboard.py
│   │   │   │   ├── attendance_warnings.html
│   │   │   │   └── test_attendance_request.py
│   │   │   ├── compensatory_leave_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── compensatory_leave_request.js
│   │   │   │   ├── compensatory_leave_request.json
│   │   │   │   ├── compensatory_leave_request.py
│   │   │   │   └── test_compensatory_leave_request.py
│   │   │   ├── daily_work_summary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary.js
│   │   │   │   ├── daily_work_summary.json
│   │   │   │   ├── daily_work_summary.py
│   │   │   │   ├── test_daily_work_summary.py
│   │   │   │   └── test_data/
│   │   │   │       └── test-reply.raw
│   │   │   ├── daily_work_summary_group/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary_group.js
│   │   │   │   ├── daily_work_summary_group.json
│   │   │   │   └── daily_work_summary_group.py
│   │   │   ├── daily_work_summary_group_user/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary_group_user.json
│   │   │   │   └── daily_work_summary_group_user.py
│   │   │   ├── department_approver/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── department_approver.json
│   │   │   │   └── department_approver.py
│   │   │   ├── designation_skill/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── designation_skill.json
│   │   │   │   └── designation_skill.py
│   │   │   ├── earned_leave_schedule/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── earned_leave_schedule.json
│   │   │   │   └── earned_leave_schedule.py
│   │   │   ├── employee_advance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_advance.js
│   │   │   │   ├── employee_advance.json
│   │   │   │   ├── employee_advance.py
│   │   │   │   ├── employee_advance_dashboard.py
│   │   │   │   └── test_employee_advance.py
│   │   │   ├── employee_attendance_tool/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_attendance_tool.css
│   │   │   │   ├── employee_attendance_tool.js
│   │   │   │   ├── employee_attendance_tool.json
│   │   │   │   ├── employee_attendance_tool.py
│   │   │   │   └── test_employee_attendance_tool.py
│   │   │   ├── employee_boarding_activity/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_boarding_activity.json
│   │   │   │   └── employee_boarding_activity.py
│   │   │   ├── employee_checkin/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_checkin.js
│   │   │   │   ├── employee_checkin.json
│   │   │   │   ├── employee_checkin.py
│   │   │   │   ├── employee_checkin_list.js
│   │   │   │   └── test_employee_checkin.py
│   │   │   ├── employee_feedback_criteria/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_feedback_criteria.js
│   │   │   │   ├── employee_feedback_criteria.json
│   │   │   │   ├── employee_feedback_criteria.py
│   │   │   │   └── test_employee_feedback_criteria.py
│   │   │   ├── employee_feedback_rating/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_feedback_rating.json
│   │   │   │   └── employee_feedback_rating.py
│   │   │   ├── employee_grade/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_grade.js
│   │   │   │   ├── employee_grade.json
│   │   │   │   ├── employee_grade.py
│   │   │   │   ├── employee_grade_dashboard.py
│   │   │   │   └── test_employee_grade.py
│   │   │   ├── employee_grievance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_grievance.js
│   │   │   │   ├── employee_grievance.json
│   │   │   │   ├── employee_grievance.py
│   │   │   │   ├── employee_grievance_list.js
│   │   │   │   └── test_employee_grievance.py
│   │   │   ├── employee_health_insurance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_health_insurance.js
│   │   │   │   ├── employee_health_insurance.json
│   │   │   │   ├── employee_health_insurance.py
│   │   │   │   └── test_employee_health_insurance.py
│   │   │   ├── employee_onboarding/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_onboarding.js
│   │   │   │   ├── employee_onboarding.json
│   │   │   │   ├── employee_onboarding.py
│   │   │   │   ├── employee_onboarding_list.js
│   │   │   │   └── test_employee_onboarding.py
│   │   │   ├── employee_onboarding_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_onboarding_template.js
│   │   │   │   ├── employee_onboarding_template.json
│   │   │   │   ├── employee_onboarding_template.py
│   │   │   │   ├── employee_onboarding_template_dashboard.py
│   │   │   │   └── test_employee_onboarding_template.py
│   │   │   ├── employee_performance_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_performance_feedback.js
│   │   │   │   ├── employee_performance_feedback.json
│   │   │   │   ├── employee_performance_feedback.py
│   │   │   │   └── test_employee_performance_feedback.py
│   │   │   ├── employee_promotion/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_promotion.js
│   │   │   │   ├── employee_promotion.json
│   │   │   │   ├── employee_promotion.py
│   │   │   │   └── test_employee_promotion.py
│   │   │   ├── employee_property_history/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_property_history.json
│   │   │   │   └── employee_property_history.py
│   │   │   ├── employee_referral/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_referral.js
│   │   │   │   ├── employee_referral.json
│   │   │   │   ├── employee_referral.py
│   │   │   │   ├── employee_referral_dashboard.py
│   │   │   │   ├── employee_referral_list.js
│   │   │   │   └── test_employee_referral.py
│   │   │   ├── employee_separation/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_separation.js
│   │   │   │   ├── employee_separation.json
│   │   │   │   ├── employee_separation.py
│   │   │   │   ├── employee_separation_list.js
│   │   │   │   └── test_employee_separation.py
│   │   │   ├── employee_separation_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_separation_template.js
│   │   │   │   ├── employee_separation_template.json
│   │   │   │   ├── employee_separation_template.py
│   │   │   │   ├── employee_separation_template_dashboard.py
│   │   │   │   └── test_employee_separation_template.py
│   │   │   ├── employee_skill/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_skill.json
│   │   │   │   └── employee_skill.py
│   │   │   ├── employee_skill_map/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_skill_map.js
│   │   │   │   ├── employee_skill_map.json
│   │   │   │   └── employee_skill_map.py
│   │   │   ├── employee_training/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_training.json
│   │   │   │   └── employee_training.py
│   │   │   ├── employee_transfer/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_transfer.js
│   │   │   │   ├── employee_transfer.json
│   │   │   │   ├── employee_transfer.py
│   │   │   │   └── test_employee_transfer.py
│   │   │   ├── employment_type/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employment_type.json
│   │   │   │   ├── employment_type.py
│   │   │   │   ├── test_employment_type.py
│   │   │   │   └── test_records.json
│   │   │   ├── exit_interview/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── exit_interview.js
│   │   │   │   ├── exit_interview.json
│   │   │   │   ├── exit_interview.py
│   │   │   │   ├── exit_interview_list.js
│   │   │   │   ├── exit_questionnaire_notification_template.html
│   │   │   │   └── test_exit_interview.py
│   │   │   ├── expected_skill_set/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expected_skill_set.json
│   │   │   │   └── expected_skill_set.py
│   │   │   ├── expense_claim/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim.js
│   │   │   │   ├── expense_claim.json
│   │   │   │   ├── expense_claim.py
│   │   │   │   ├── expense_claim_dashboard.py
│   │   │   │   ├── expense_claim_list.js
│   │   │   │   └── test_expense_claim.py
│   │   │   ├── expense_claim_account/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_account.json
│   │   │   │   └── expense_claim_account.py
│   │   │   ├── expense_claim_advance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_advance.json
│   │   │   │   └── expense_claim_advance.py
│   │   │   ├── expense_claim_detail/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_detail.json
│   │   │   │   └── expense_claim_detail.py
│   │   │   ├── expense_claim_type/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_type.js
│   │   │   │   ├── expense_claim_type.json
│   │   │   │   ├── expense_claim_type.py
│   │   │   │   └── test_expense_claim_type.py
│   │   │   ├── expense_taxes_and_charges/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_taxes_and_charges.json
│   │   │   │   └── expense_taxes_and_charges.py
│   │   │   ├── full_and_final_asset/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── full_and_final_asset.js
│   │   │   │   ├── full_and_final_asset.json
│   │   │   │   ├── full_and_final_asset.py
│   │   │   │   └── test_full_and_final_asset.py
│   │   │   ├── full_and_final_outstanding_statement/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── full_and_final_outstanding_statement.json
│   │   │   │   └── full_and_final_outstanding_statement.py
│   │   │   ├── full_and_final_statement/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── full_and_final_statement.js
│   │   │   │   ├── full_and_final_statement.json
│   │   │   │   ├── full_and_final_statement.py
│   │   │   │   ├── full_and_final_statement_list.js
│   │   │   │   ├── full_and_final_statement_loan_utils.py
│   │   │   │   └── test_full_and_final_statement.py
│   │   │   ├── goal/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── goal.js
│   │   │   │   ├── goal.json
│   │   │   │   ├── goal.py
│   │   │   │   ├── goal_list.js
│   │   │   │   ├── goal_tree.js
│   │   │   │   └── test_goal.py
│   │   │   ├── grievance_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── grievance_type.js
│   │   │   │   ├── grievance_type.json
│   │   │   │   ├── grievance_type.py
│   │   │   │   └── test_grievance_type.py
│   │   │   ├── holiday_list_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── holiday_list_assignment.js
│   │   │   │   ├── holiday_list_assignment.json
│   │   │   │   ├── holiday_list_assignment.py
│   │   │   │   └── test_holiday_list_assignment.py
│   │   │   ├── hr_settings/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── hr_settings.js
│   │   │   │   ├── hr_settings.json
│   │   │   │   ├── hr_settings.py
│   │   │   │   └── test_hr_settings.py
│   │   │   ├── identification_document_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── identification_document_type.js
│   │   │   │   ├── identification_document_type.json
│   │   │   │   ├── identification_document_type.py
│   │   │   │   └── test_identification_document_type.py
│   │   │   ├── interest/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interest.js
│   │   │   │   ├── interest.json
│   │   │   │   ├── interest.py
│   │   │   │   └── test_interest.py
│   │   │   ├── interview/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview.js
│   │   │   │   ├── interview.json
│   │   │   │   ├── interview.py
│   │   │   │   ├── interview_calendar.js
│   │   │   │   ├── interview_feedback_reminder_template.html
│   │   │   │   ├── interview_list.js
│   │   │   │   ├── interview_reminder_notification_template.html
│   │   │   │   └── test_interview.py
│   │   │   ├── interview_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_detail.json
│   │   │   │   └── interview_detail.py
│   │   │   ├── interview_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_feedback.js
│   │   │   │   ├── interview_feedback.json
│   │   │   │   ├── interview_feedback.py
│   │   │   │   └── test_interview_feedback.py
│   │   │   ├── interview_round/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_round.js
│   │   │   │   ├── interview_round.json
│   │   │   │   ├── interview_round.py
│   │   │   │   └── test_interview_round.py
│   │   │   ├── interview_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_type.js
│   │   │   │   ├── interview_type.json
│   │   │   │   ├── interview_type.py
│   │   │   │   └── test_interview_type.py
│   │   │   ├── interviewer/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interviewer.json
│   │   │   │   └── interviewer.py
│   │   │   ├── job_applicant/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_applicant.js
│   │   │   │   ├── job_applicant.json
│   │   │   │   ├── job_applicant.py
│   │   │   │   ├── job_applicant_dashboard.html
│   │   │   │   ├── job_applicant_dashboard.py
│   │   │   │   ├── job_applicant_list.js
│   │   │   │   └── test_job_applicant.py
│   │   │   ├── job_applicant_source/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_applicant_source.js
│   │   │   │   ├── job_applicant_source.json
│   │   │   │   ├── job_applicant_source.py
│   │   │   │   └── test_job_applicant_source.py
│   │   │   ├── job_offer/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_offer.js
│   │   │   │   ├── job_offer.json
│   │   │   │   ├── job_offer.py
│   │   │   │   ├── job_offer_list.js
│   │   │   │   └── test_job_offer.py
│   │   │   ├── job_offer_term/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_offer_term.json
│   │   │   │   └── job_offer_term.py
│   │   │   ├── job_offer_term_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_offer_term_template.js
│   │   │   │   ├── job_offer_term_template.json
│   │   │   │   ├── job_offer_term_template.py
│   │   │   │   └── test_job_offer_term_template.py
│   │   │   ├── job_opening/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_opening.js
│   │   │   │   ├── job_opening.json
│   │   │   │   ├── job_opening.py
│   │   │   │   ├── job_opening_dashboard.py
│   │   │   │   ├── templates/
│   │   │   │   │   ├── job_opening.html
│   │   │   │   │   └── job_opening_row.html
│   │   │   │   └── test_job_opening.py
│   │   │   ├── job_opening_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_opening_template.js
│   │   │   │   ├── job_opening_template.json
│   │   │   │   ├── job_opening_template.py
│   │   │   │   └── test_job_opening_template.py
│   │   │   ├── job_requisition/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_requisition.js
│   │   │   │   ├── job_requisition.json
│   │   │   │   ├── job_requisition.py
│   │   │   │   ├── job_requisition_list.js
│   │   │   │   └── test_job_requisition.py
│   │   │   ├── kra/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── kra.js
│   │   │   │   ├── kra.json
│   │   │   │   ├── kra.py
│   │   │   │   └── test_kra.py
│   │   │   ├── leave_adjustment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_adjustment.js
│   │   │   │   ├── leave_adjustment.json
│   │   │   │   ├── leave_adjustment.py
│   │   │   │   └── test_leave_adjustment.py
│   │   │   ├── leave_allocation/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_allocation.js
│   │   │   │   ├── leave_allocation.json
│   │   │   │   ├── leave_allocation.py
│   │   │   │   ├── leave_allocation_dashboard.py
│   │   │   │   ├── leave_allocation_list.js
│   │   │   │   ├── test_earned_leave_schedule.py
│   │   │   │   ├── test_earned_leaves.py
│   │   │   │   └── test_leave_allocation.py
│   │   │   ├── leave_application/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_application.js
│   │   │   │   ├── leave_application.json
│   │   │   │   ├── leave_application.py
│   │   │   │   ├── leave_application_calendar.js
│   │   │   │   ├── leave_application_dashboard.html
│   │   │   │   ├── leave_application_dashboard.py
│   │   │   │   ├── leave_application_email_template.html
│   │   │   │   ├── leave_application_list.js
│   │   │   │   ├── test_leave_application.py
│   │   │   │   └── test_records.json
│   │   │   ├── leave_block_list/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_block_list.js
│   │   │   │   ├── leave_block_list.json
│   │   │   │   ├── leave_block_list.py
│   │   │   │   ├── leave_block_list_dashboard.py
│   │   │   │   ├── test_leave_block_list.py
│   │   │   │   └── test_records.json
│   │   │   ├── leave_block_list_allow/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_block_list_allow.json
│   │   │   │   └── leave_block_list_allow.py
│   │   │   ├── leave_block_list_date/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_block_list_date.json
│   │   │   │   └── leave_block_list_date.py
│   │   │   ├── leave_control_panel/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_control_panel.js
│   │   │   │   ├── leave_control_panel.json
│   │   │   │   ├── leave_control_panel.py
│   │   │   │   └── test_leave_control_panel.py
│   │   │   ├── leave_encashment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_encashment.js
│   │   │   │   ├── leave_encashment.json
│   │   │   │   ├── leave_encashment.py
│   │   │   │   └── test_leave_encashment.py
│   │   │   ├── leave_ledger_entry/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_ledger_entry.js
│   │   │   │   ├── leave_ledger_entry.json
│   │   │   │   ├── leave_ledger_entry.py
│   │   │   │   ├── leave_ledger_entry_list.js
│   │   │   │   └── test_leave_ledger_entry.py
│   │   │   ├── leave_period/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_period.js
│   │   │   │   ├── leave_period.json
│   │   │   │   ├── leave_period.py
│   │   │   │   ├── leave_period_dashboard.py
│   │   │   │   └── test_leave_period.py
│   │   │   ├── leave_policy/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_policy.js
│   │   │   │   ├── leave_policy.json
│   │   │   │   ├── leave_policy.py
│   │   │   │   ├── leave_policy_dashboard.py
│   │   │   │   └── test_leave_policy.py
│   │   │   ├── leave_policy_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_policy_assignment.js
│   │   │   │   ├── leave_policy_assignment.json
│   │   │   │   ├── leave_policy_assignment.py
│   │   │   │   ├── leave_policy_assignment_dashboard.py
│   │   │   │   ├── leave_policy_assignment_list.js
│   │   │   │   └── test_leave_policy_assignment.py
│   │   │   ├── leave_policy_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_policy_detail.js
│   │   │   │   ├── leave_policy_detail.json
│   │   │   │   ├── leave_policy_detail.py
│   │   │   │   └── test_leave_policy_detail.py
│   │   │   ├── leave_type/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_type.js
│   │   │   │   ├── leave_type.json
│   │   │   │   ├── leave_type.py
│   │   │   │   ├── leave_type_dashboard.py
│   │   │   │   ├── test_leave_type.py
│   │   │   │   └── test_records.json
│   │   │   ├── offer_term/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── offer_term.js
│   │   │   │   ├── offer_term.json
│   │   │   │   ├── offer_term.py
│   │   │   │   └── test_offer_term.py
│   │   │   ├── overtime_details/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_details.json
│   │   │   │   └── overtime_details.py
│   │   │   ├── overtime_salary_component/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_salary_component.json
│   │   │   │   └── overtime_salary_component.py
│   │   │   ├── overtime_slip/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_slip.js
│   │   │   │   ├── overtime_slip.json
│   │   │   │   ├── overtime_slip.py
│   │   │   │   └── test_overtime_slip.py
│   │   │   ├── overtime_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_type.js
│   │   │   │   ├── overtime_type.json
│   │   │   │   ├── overtime_type.py
│   │   │   │   └── test_overtime_type.py
│   │   │   ├── purpose_of_travel/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── purpose_of_travel.js
│   │   │   │   ├── purpose_of_travel.json
│   │   │   │   ├── purpose_of_travel.py
│   │   │   │   └── test_purpose_of_travel.py
│   │   │   ├── pwa_notification/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── pwa_notification.js
│   │   │   │   ├── pwa_notification.json
│   │   │   │   ├── pwa_notification.py
│   │   │   │   └── test_pwa_notification.py
│   │   │   ├── shift_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_assignment.js
│   │   │   │   ├── shift_assignment.json
│   │   │   │   ├── shift_assignment.py
│   │   │   │   ├── shift_assignment_calendar.js
│   │   │   │   ├── shift_assignment_list.js
│   │   │   │   └── test_shift_assignment.py
│   │   │   ├── shift_assignment_tool/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_assignment_tool.js
│   │   │   │   ├── shift_assignment_tool.json
│   │   │   │   ├── shift_assignment_tool.py
│   │   │   │   └── test_shift_assignment_tool.py
│   │   │   ├── shift_location/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_location.js
│   │   │   │   ├── shift_location.json
│   │   │   │   ├── shift_location.py
│   │   │   │   ├── shift_location_list.js
│   │   │   │   └── test_shift_location.py
│   │   │   ├── shift_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_request.js
│   │   │   │   ├── shift_request.json
│   │   │   │   ├── shift_request.py
│   │   │   │   ├── shift_request_dashboard.py
│   │   │   │   ├── shift_request_list.js
│   │   │   │   └── test_shift_request.py
│   │   │   ├── shift_schedule/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_schedule.js
│   │   │   │   ├── shift_schedule.json
│   │   │   │   ├── shift_schedule.py
│   │   │   │   └── shift_schedule_list.js
│   │   │   ├── shift_schedule_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_schedule_assignment.js
│   │   │   │   ├── shift_schedule_assignment.json
│   │   │   │   ├── shift_schedule_assignment.py
│   │   │   │   ├── shift_schedule_assignment_list.js
│   │   │   │   └── test_shift_schedule_assignment.py
│   │   │   ├── shift_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_type.js
│   │   │   │   ├── shift_type.json
│   │   │   │   ├── shift_type.py
│   │   │   │   ├── shift_type_dashboard.py
│   │   │   │   ├── shift_type_list.js
│   │   │   │   ├── test_records.json
│   │   │   │   └── test_shift_type.py
│   │   │   ├── skill/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── skill.js
│   │   │   │   ├── skill.json
│   │   │   │   └── skill.py
│   │   │   ├── skill_assessment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── skill_assessment.json
│   │   │   │   └── skill_assessment.py
│   │   │   ├── staffing_plan/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── staffing_plan.js
│   │   │   │   ├── staffing_plan.json
│   │   │   │   ├── staffing_plan.py
│   │   │   │   ├── staffing_plan_dashboard.py
│   │   │   │   └── test_staffing_plan.py
│   │   │   ├── staffing_plan_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── staffing_plan_detail.json
│   │   │   │   └── staffing_plan_detail.py
│   │   │   ├── training_event/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_event.py
│   │   │   │   ├── training_event.js
│   │   │   │   ├── training_event.json
│   │   │   │   ├── training_event.py
│   │   │   │   ├── training_event_calendar.js
│   │   │   │   └── training_event_dashboard.py
│   │   │   ├── training_event_employee/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── training_event_employee.json
│   │   │   │   └── training_event_employee.py
│   │   │   ├── training_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_feedback.py
│   │   │   │   ├── training_feedback.js
│   │   │   │   ├── training_feedback.json
│   │   │   │   └── training_feedback.py
│   │   │   ├── training_program/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_program.py
│   │   │   │   ├── training_program.js
│   │   │   │   ├── training_program.json
│   │   │   │   ├── training_program.py
│   │   │   │   └── training_program_dashboard.py
│   │   │   ├── training_result/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_result.py
│   │   │   │   ├── training_result.js
│   │   │   │   ├── training_result.json
│   │   │   │   └── training_result.py
│   │   │   ├── training_result_employee/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── training_result_employee.json
│   │   │   │   └── training_result_employee.py
│   │   │   ├── travel_itinerary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── travel_itinerary.json
│   │   │   │   └── travel_itinerary.py
│   │   │   ├── travel_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_travel_request.py
│   │   │   │   ├── travel_request.js
│   │   │   │   ├── travel_request.json
│   │   │   │   └── travel_request.py
│   │   │   ├── travel_request_costing/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── travel_request_costing.json
│   │   │   │   └── travel_request_costing.py
│   │   │   ├── upload_attendance/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_upload_attendance.py
│   │   │   │   ├── upload_attendance.js
│   │   │   │   ├── upload_attendance.json
│   │   │   │   └── upload_attendance.py
│   │   │   ├── vehicle_log/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_vehicle_log.py
│   │   │   │   ├── vehicle_log.js
│   │   │   │   ├── vehicle_log.json
│   │   │   │   └── vehicle_log.py
│   │   │   ├── vehicle_service/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── vehicle_service.json
│   │   │   │   └── vehicle_service.py
│   │   │   └── vehicle_service_item/
│   │   │       ├── __init__.py
│   │   │       ├── test_vehicle_service_item.py
│   │   │       ├── vehicle_service_item.js
│   │   │       ├── vehicle_service_item.json
│   │   │       └── vehicle_service_item.py
│   │   ├── employee_property_update.js
│   │   ├── hr_dashboard/
│   │   │   ├── attendance/
│   │   │   │   └── attendance.json
│   │   │   ├── employee_lifecycle/
│   │   │   │   └── employee_lifecycle.json
│   │   │   ├── expense_claims/
│   │   │   │   └── expense_claims.json
│   │   │   ├── human_resource/
│   │   │   │   └── human_resource.json
│   │   │   └── recruitment/
│   │   │       └── recruitment.json
│   │   ├── notification/
│   │   │   ├── __init__.py
│   │   │   ├── exit_interview_scheduled/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── exit_interview_scheduled.json
│   │   │   │   ├── exit_interview_scheduled.md
│   │   │   │   └── exit_interview_scheduled.py
│   │   │   ├── training_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── training_feedback.html
│   │   │   │   ├── training_feedback.json
│   │   │   │   ├── training_feedback.md
│   │   │   │   └── training_feedback.py
│   │   │   └── training_scheduled/
│   │   │       ├── __init__.py
│   │   │       ├── training_scheduled.html
│   │   │       ├── training_scheduled.json
│   │   │       ├── training_scheduled.md
│   │   │       └── training_scheduled.py
│   │   ├── number_card/
│   │   │   ├── accepted_job_applicants/
│   │   │   │   └── accepted_job_applicants.json
│   │   │   ├── applicant_to_hire_percentage/
│   │   │   │   └── applicant_to_hire_percentage.json
│   │   │   ├── approved_claims_(this_month)/
│   │   │   │   └── approved_claims_(this_month).json
│   │   │   ├── early_exit_(this_month)/
│   │   │   │   └── early_exit_(this_month).json
│   │   │   ├── employee_exits_(this_year)/
│   │   │   │   └── employee_exits_(this_year).json
│   │   │   ├── employees_joining_(this_quarter)/
│   │   │   │   └── employees_joining_(this_quarter).json
│   │   │   ├── employees_relieving_(this_quarter)/
│   │   │   │   └── employees_relieving_(this_quarter).json
│   │   │   ├── expense_claims_(this_month)/
│   │   │   │   └── expense_claims_(this_month).json
│   │   │   ├── holidays_in_this_month/
│   │   │   │   └── holidays_in_this_month.json
│   │   │   ├── job_offer_acceptance_rate/
│   │   │   │   └── job_offer_acceptance_rate.json
│   │   │   ├── job_offers_(this_month)/
│   │   │   │   └── job_offers_(this_month).json
│   │   │   ├── job_openings/
│   │   │   │   └── job_openings.json
│   │   │   ├── late_entry_(this_month)/
│   │   │   │   └── late_entry_(this_month).json
│   │   │   ├── new_hires_(this_year)/
│   │   │   │   └── new_hires_(this_year).json
│   │   │   ├── number_of_employees_on_leave_(this_month)/
│   │   │   │   └── number_of_employees_on_leave_(this_month).json
│   │   │   ├── number_of_employees_on_leave_(today)/
│   │   │   │   └── number_of_employees_on_leave_(today).json
│   │   │   ├── onboardings_(this_month)/
│   │   │   │   └── onboardings_(this_month).json
│   │   │   ├── promotions_(this_month)/
│   │   │   │   └── promotions_(this_month).json
│   │   │   ├── rejected_claims_(this_month)/
│   │   │   │   └── rejected_claims_(this_month).json
│   │   │   ├── rejected_job_applicants/
│   │   │   │   └── rejected_job_applicants.json
│   │   │   ├── separations_(this_month)/
│   │   │   │   └── separations_(this_month).json
│   │   │   ├── time_to_fill/
│   │   │   │   └── time_to_fill.json
│   │   │   ├── total_absent_(this_month)/
│   │   │   │   └── total_absent_(this_month).json
│   │   │   ├── total_applicants_(this_month)/
│   │   │   │   └── total_applicants_(this_month).json
│   │   │   ├── total_employees/
│   │   │   │   └── total_employees.json
│   │   │   ├── total_present_(this_month)/
│   │   │   │   └── total_present_(this_month).json
│   │   │   ├── trainings_(this_month)/
│   │   │   │   └── trainings_(this_month).json
│   │   │   └── transfers_(this_month)/
│   │   │       └── transfers_(this_month).json
│   │   ├── page/
│   │   │   ├── __init__.py
│   │   │   ├── organizational_chart/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── organizational_chart.js
│   │   │   │   ├── organizational_chart.json
│   │   │   │   ├── organizational_chart.py
│   │   │   │   └── test_organizational_chart.py
│   │   │   └── team_updates/
│   │   │       ├── __init__.py
│   │   │       ├── team_update_row.html
│   │   │       ├── team_updates.css
│   │   │       ├── team_updates.js
│   │   │       ├── team_updates.json
│   │   │       └── team_updates.py
│   │   ├── print_format/
│   │   │   ├── __init__.py
│   │   │   ├── job_offer/
│   │   │   │   ├── __init__.py
│   │   │   │   └── job_offer.json
│   │   │   └── standard_appointment_letter/
│   │   │       ├── __init__.py
│   │   │       ├── standard_appointment_letter.html
│   │   │       └── standard_appointment_letter.json
│   │   ├── report/
│   │   │   ├── __init__.py
│   │   │   ├── appraisal_overview/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_overview.js
│   │   │   │   ├── appraisal_overview.json
│   │   │   │   ├── appraisal_overview.py
│   │   │   │   └── test_appraisal_overview.py
│   │   │   ├── daily_work_summary_replies/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary_replies.js
│   │   │   │   ├── daily_work_summary_replies.json
│   │   │   │   └── daily_work_summary_replies.py
│   │   │   ├── employee_advance_summary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_advance_summary.js
│   │   │   │   ├── employee_advance_summary.json
│   │   │   │   └── employee_advance_summary.py
│   │   │   ├── employee_analytics/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_analytics.js
│   │   │   │   ├── employee_analytics.json
│   │   │   │   ├── employee_analytics.py
│   │   │   │   └── test_employee_analytics.py
│   │   │   ├── employee_birthday/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_birthday.js
│   │   │   │   ├── employee_birthday.json
│   │   │   │   ├── employee_birthday.py
│   │   │   │   └── test_employee_birthday.py
│   │   │   ├── employee_exits/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_exits.js
│   │   │   │   ├── employee_exits.json
│   │   │   │   ├── employee_exits.py
│   │   │   │   └── test_employee_exits.py
│   │   │   ├── employee_hours_utilization_based_on_timesheet/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_hours_utilization_based_on_timesheet.js
│   │   │   │   ├── employee_hours_utilization_based_on_timesheet.json
│   │   │   │   ├── employee_hours_utilization_based_on_timesheet.py
│   │   │   │   └── test_employee_util.py
│   │   │   ├── employee_information/
│   │   │   │   ├── __init__.py
│   │   │   │   └── employee_information.json
│   │   │   ├── employee_leave_balance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_leave_balance.js
│   │   │   │   ├── employee_leave_balance.json
│   │   │   │   ├── employee_leave_balance.py
│   │   │   │   └── test_employee_leave_balance.py
│   │   │   ├── employee_leave_balance_summary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_leave_balance_summary.js
│   │   │   │   ├── employee_leave_balance_summary.json
│   │   │   │   ├── employee_leave_balance_summary.py
│   │   │   │   └── test_employee_leave_balance_summary.py
│   │   │   ├── employees_working_on_a_holiday/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employees_working_on_a_holiday.js
│   │   │   │   ├── employees_working_on_a_holiday.json
│   │   │   │   ├── employees_working_on_a_holiday.py
│   │   │   │   └── test_employees_working_on_a_holiday.py
│   │   │   ├── leave_ledger/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_ledger.js
│   │   │   │   ├── leave_ledger.json
│   │   │   │   ├── leave_ledger.py
│   │   │   │   └── test_leave_ledger.py
│   │   │   ├── monthly_attendance_sheet/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── monthly_attendance_sheet.js
│   │   │   │   ├── monthly_attendance_sheet.json
│   │   │   │   ├── monthly_attendance_sheet.py
│   │   │   │   └── test_monthly_attendance_sheet.py
│   │   │   ├── project_profitability/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── project_profitability.js
│   │   │   │   ├── project_profitability.json
│   │   │   │   ├── project_profitability.py
│   │   │   │   └── test_project_profitability.py
│   │   │   ├── recruitment_analytics/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── recruitment_analytics.js
│   │   │   │   ├── recruitment_analytics.json
│   │   │   │   └── recruitment_analytics.py
│   │   │   ├── shift_attendance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_attendance.js
│   │   │   │   ├── shift_attendance.json
│   │   │   │   ├── shift_attendance.py
│   │   │   │   └── test_shift_attendance.py
│   │   │   ├── unpaid_expense_claim/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── unpaid_expense_claim.js
│   │   │   │   ├── unpaid_expense_claim.json
│   │   │   │   └── unpaid_expense_claim.py
│   │   │   └── vehicle_expenses/
│   │   │       ├── __init__.py
│   │   │       ├── test_vehicle_expenses.py
│   │   │       ├── vehicle_expenses.js
│   │   │       ├── vehicle_expenses.json
│   │   │       └── vehicle_expenses.py
│   │   ├── utils.py
│   │   ├── web_form/
│   │   │   ├── __init__.py
│   │   │   └── job_application/
│   │   │       ├── __init__.py
│   │   │       ├── job_application.js
│   │   │       ├── job_application.json
│   │   │       └── job_application.py
│   │   └── workspace/
│   │       ├── expenses/
│   │       │   └── expenses.json
│   │       ├── leaves/
│   │       │   └── leaves.json
│   │       ├── people/
│   │       │   └── people.json
│   │       ├── performance/
│   │       │   └── performance.json
│   │       ├── recruitment/
│   │       │   └── recruitment.json
│   │       ├── shift_&_attendance/
│   │       │   └── shift_&_attendance.json
│   │       └── tenure/
│   │           └── tenure.json
│   ├── install.py
│   ├── locale/
│   │   ├── af.po
│   │   ├── ar.po
│   │   ├── bs.po
│   │   ├── cs.po
│   │   ├── da.po
│   │   ├── de.po
│   │   ├── eo.po
│   │   ├── es.po
│   │   ├── fa.po
│   │   ├── fi.po
│   │   ├── fr.po
│   │   ├── hr.po
│   │   ├── hu.po
│   │   ├── id.po
│   │   ├── it.po
│   │   ├── main.pot
│   │   ├── my.po
│   │   ├── nb.po
│   │   ├── nl.po
│   │   ├── pl.po
│   │   ├── pt.po
│   │   ├── pt_BR.po
│   │   ├── ru.po
│   │   ├── sl.po
│   │   ├── sr.po
│   │   ├── sr_CS.po
│   │   ├── sv.po
│   │   ├── ta.po
│   │   ├── th.po
│   │   ├── tr.po
│   │   ├── vi.po
│   │   ├── zh.po
│   │   └── zh_TW.po
│   ├── mixins/
│   │   ├── appraisal.py
│   │   └── pwa_notifications.py
│   ├── modules.txt
│   ├── overrides/
│   │   ├── company.py
│   │   ├── dashboard_overrides.py
│   │   ├── employee_master.py
│   │   ├── employee_payment_entry.py
│   │   ├── employee_project.py
│   │   └── employee_timesheet.py
│   ├── patches/
│   │   ├── post_install/
│   │   │   ├── create_country_fixtures.py
│   │   │   ├── delete_employee_transfer_property_doctype.py
│   │   │   ├── move_doctype_reports_and_notification_from_hr_to_payroll.py
│   │   │   ├── move_payroll_setting_separately_from_hr_settings.py
│   │   │   ├── move_tax_slabs_from_payroll_period_to_income_tax_slab.py
│   │   │   ├── rename_stop_to_send_birthday_reminders.py
│   │   │   ├── set_company_in_leave_ledger_entry.py
│   │   │   ├── set_department_for_doctypes.py
│   │   │   ├── set_payroll_cost_centers.py
│   │   │   ├── set_payroll_entry_status.py
│   │   │   ├── set_training_event_attendance.py
│   │   │   ├── update_allocate_on_in_leave_type.py
│   │   │   ├── update_employee_advance_status.py
│   │   │   ├── update_expense_claim_status_for_paid_advances.py
│   │   │   ├── update_performance_module_changes.py
│   │   │   ├── update_reason_for_resignation_in_employee.py
│   │   │   ├── update_start_end_date_for_old_shift_assignment.py
│   │   │   └── updates_for_multi_currency_payroll.py
│   │   ├── v14_0/
│   │   │   ├── add_expense_claim_to_repost_settings.py
│   │   │   ├── create_custom_field_for_appraisal_template.py
│   │   │   ├── create_custom_field_in_loan.py
│   │   │   ├── create_marginal_relief_field_for_india_localisation.py
│   │   │   ├── create_vehicle_service_item.py
│   │   │   ├── update_ess_user_access.py
│   │   │   ├── update_loan_repayment_repay_from_salary.py
│   │   │   ├── update_payroll_frequency_to_none_if_salary_slip_is_based_on_timesheet.py
│   │   │   ├── update_repay_from_salary_and_payroll_payable_account_fields.py
│   │   │   └── update_title_in_employee_onboarding_and_separation_templates.py
│   │   ├── v15_0/
│   │   │   ├── add_leave_type_permission_for_ess.py
│   │   │   ├── add_loan_docperms_to_ess.py
│   │   │   ├── call_set_total_advance_paid_on_advance_documents.py
│   │   │   ├── check_version_compatibility_with_frappe.py
│   │   │   ├── create_accounting_dimensions_in_leave_encashment.py
│   │   │   ├── create_marginal_relief_field_for_india_localisation.py
│   │   │   ├── enable_allow_checkin_setting.py
│   │   │   ├── fix_timesheet_status.py
│   │   │   ├── make_hr_settings_tab_in_company_master.py
│   │   │   ├── migrate_loan_type_to_loan_product.py
│   │   │   ├── migrate_shift_assignment_schedule_to_shift_schedule.py
│   │   │   ├── notify_about_loan_app_separation.py
│   │   │   ├── rename_and_update_leave_encashment_fields.py
│   │   │   ├── rename_claim_date_to_payroll_date_in_employee_benefit_claim.py
│   │   │   ├── rename_enable_late_entry_early_exit_grace_period.py
│   │   │   ├── set_default_asset_action_in_fnf.py
│   │   │   ├── set_half_day_status_to_present_in_exisiting_half_day_attendance.py
│   │   │   ├── update_advance_payment_ledger_amount.py
│   │   │   └── update_payment_status_for_leave_encashment.py
│   │   ├── v16_0/
│   │   │   ├── create_custom_field_for_employee_advance_in_employee_master.py
│   │   │   ├── create_holiday_list_assignments.py
│   │   │   ├── delete_old_workspaces.py
│   │   │   ├── set_base_paid_amount_in_employee_advance.py
│   │   │   └── set_currency_and_base_fields_in_expense_claim.py
│   │   └── v1_0/
│   │       └── rearrange_employee_fields.py
│   ├── patches.txt
│   ├── payroll/
│   │   ├── __init__.py
│   │   ├── dashboard_chart/
│   │   │   ├── department_wise_salary(last_month)/
│   │   │   │   └── department_wise_salary(last_month).json
│   │   │   ├── designation_wise_salary(last_month)/
│   │   │   │   └── designation_wise_salary(last_month).json
│   │   │   └── outgoing_salary/
│   │   │       └── outgoing_salary.json
│   │   ├── data/
│   │   │   └── salary_components.json
│   │   ├── doctype/
│   │   │   ├── __init__.py
│   │   │   ├── additional_salary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── additional_salary.js
│   │   │   │   ├── additional_salary.json
│   │   │   │   ├── additional_salary.py
│   │   │   │   └── test_additional_salary.py
│   │   │   ├── arrear/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── arrear.js
│   │   │   │   ├── arrear.json
│   │   │   │   ├── arrear.py
│   │   │   │   └── test_arrear.py
│   │   │   ├── bulk_salary_structure_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── bulk_salary_structure_assignment.js
│   │   │   │   ├── bulk_salary_structure_assignment.json
│   │   │   │   ├── bulk_salary_structure_assignment.py
│   │   │   │   └── test_bulk_salary_structure_assignment.py
│   │   │   ├── employee_benefit_application/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_application.js
│   │   │   │   ├── employee_benefit_application.json
│   │   │   │   ├── employee_benefit_application.py
│   │   │   │   └── test_employee_benefit_application.py
│   │   │   ├── employee_benefit_application_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_application_detail.json
│   │   │   │   └── employee_benefit_application_detail.py
│   │   │   ├── employee_benefit_claim/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_claim.js
│   │   │   │   ├── employee_benefit_claim.json
│   │   │   │   ├── employee_benefit_claim.py
│   │   │   │   └── test_employee_benefit_claim.py
│   │   │   ├── employee_benefit_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_detail.json
│   │   │   │   └── employee_benefit_detail.py
│   │   │   ├── employee_benefit_ledger/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_ledger.js
│   │   │   │   ├── employee_benefit_ledger.json
│   │   │   │   ├── employee_benefit_ledger.py
│   │   │   │   └── employee_benefit_ledger_list.js
│   │   │   ├── employee_cost_center/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_cost_center.json
│   │   │   │   └── employee_cost_center.py
│   │   │   ├── employee_incentive/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_incentive.js
│   │   │   │   ├── employee_incentive.json
│   │   │   │   ├── employee_incentive.py
│   │   │   │   └── test_employee_incentive.py
│   │   │   ├── employee_other_income/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_other_income.js
│   │   │   │   ├── employee_other_income.json
│   │   │   │   ├── employee_other_income.py
│   │   │   │   └── test_employee_other_income.py
│   │   │   ├── employee_tax_exemption_category/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_category.js
│   │   │   │   ├── employee_tax_exemption_category.json
│   │   │   │   ├── employee_tax_exemption_category.py
│   │   │   │   └── test_employee_tax_exemption_category.py
│   │   │   ├── employee_tax_exemption_declaration/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_declaration.js
│   │   │   │   ├── employee_tax_exemption_declaration.json
│   │   │   │   ├── employee_tax_exemption_declaration.py
│   │   │   │   └── test_employee_tax_exemption_declaration.py
│   │   │   ├── employee_tax_exemption_declaration_category/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_declaration_category.json
│   │   │   │   └── employee_tax_exemption_declaration_category.py
│   │   │   ├── employee_tax_exemption_proof_submission/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_proof_submission.js
│   │   │   │   ├── employee_tax_exemption_proof_submission.json
│   │   │   │   ├── employee_tax_exemption_proof_submission.py
│   │   │   │   └── test_employee_tax_exemption_proof_submission.py
│   │   │   ├── employee_tax_exemption_proof_submission_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_proof_submission_detail.json
│   │   │   │   └── employee_tax_exemption_proof_submission_detail.py
│   │   │   ├── employee_tax_exemption_sub_category/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_sub_category.js
│   │   │   │   ├── employee_tax_exemption_sub_category.json
│   │   │   │   ├── employee_tax_exemption_sub_category.py
│   │   │   │   └── test_employee_tax_exemption_sub_category.py
│   │   │   ├── gratuity/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity.js
│   │   │   │   ├── gratuity.json
│   │   │   │   ├── gratuity.py
│   │   │   │   ├── gratuity_dashboard.py
│   │   │   │   ├── gratuity_list.js
│   │   │   │   └── test_gratuity.py
│   │   │   ├── gratuity_applicable_component/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity_applicable_component.json
│   │   │   │   └── gratuity_applicable_component.py
│   │   │   ├── gratuity_rule/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity_rule.js
│   │   │   │   ├── gratuity_rule.json
│   │   │   │   ├── gratuity_rule.py
│   │   │   │   ├── gratuity_rule_dashboard.py
│   │   │   │   └── test_gratuity_rule.py
│   │   │   ├── gratuity_rule_slab/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity_rule_slab.json
│   │   │   │   └── gratuity_rule_slab.py
│   │   │   ├── income_tax_slab/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_slab.js
│   │   │   │   ├── income_tax_slab.json
│   │   │   │   ├── income_tax_slab.py
│   │   │   │   └── test_income_tax_slab.py
│   │   │   ├── income_tax_slab_other_charges/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_slab_other_charges.json
│   │   │   │   └── income_tax_slab_other_charges.py
│   │   │   ├── payroll_correction/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_correction.js
│   │   │   │   ├── payroll_correction.json
│   │   │   │   ├── payroll_correction.py
│   │   │   │   └── test_payroll_correction.py
│   │   │   ├── payroll_correction_child/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_correction_child.json
│   │   │   │   └── payroll_correction_child.py
│   │   │   ├── payroll_employee_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_employee_detail.json
│   │   │   │   └── payroll_employee_detail.py
│   │   │   ├── payroll_entry/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_entry.js
│   │   │   │   ├── payroll_entry.json
│   │   │   │   ├── payroll_entry.py
│   │   │   │   ├── payroll_entry_dashboard.py
│   │   │   │   ├── payroll_entry_list.js
│   │   │   │   └── test_payroll_entry.py
│   │   │   ├── payroll_period/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_period.js
│   │   │   │   ├── payroll_period.json
│   │   │   │   ├── payroll_period.py
│   │   │   │   ├── payroll_period_dashboard.py
│   │   │   │   └── test_payroll_period.py
│   │   │   ├── payroll_period_date/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_period_date.json
│   │   │   │   └── payroll_period_date.py
│   │   │   ├── payroll_settings/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_settings.js
│   │   │   │   ├── payroll_settings.json
│   │   │   │   ├── payroll_settings.py
│   │   │   │   └── test_payroll_settings.py
│   │   │   ├── retention_bonus/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── retention_bonus.js
│   │   │   │   ├── retention_bonus.json
│   │   │   │   ├── retention_bonus.py
│   │   │   │   └── test_retention_bonus.py
│   │   │   ├── salary_component/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_component.js
│   │   │   │   ├── salary_component.json
│   │   │   │   ├── salary_component.py
│   │   │   │   ├── test_records.json
│   │   │   │   └── test_salary_component.py
│   │   │   ├── salary_component_account/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_component_account.json
│   │   │   │   └── salary_component_account.py
│   │   │   ├── salary_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_detail.json
│   │   │   │   └── salary_detail.py
│   │   │   ├── salary_slip/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip.js
│   │   │   │   ├── salary_slip.json
│   │   │   │   ├── salary_slip.py
│   │   │   │   ├── salary_slip_list.js
│   │   │   │   ├── salary_slip_loan_utils.py
│   │   │   │   └── test_salary_slip.py
│   │   │   ├── salary_slip_leave/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip_leave.json
│   │   │   │   └── salary_slip_leave.py
│   │   │   ├── salary_slip_loan/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip_loan.json
│   │   │   │   └── salary_slip_loan.py
│   │   │   ├── salary_slip_timesheet/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip_timesheet.json
│   │   │   │   └── salary_slip_timesheet.py
│   │   │   ├── salary_structure/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── condition_and_formula_help.html
│   │   │   │   ├── salary_structure.js
│   │   │   │   ├── salary_structure.json
│   │   │   │   ├── salary_structure.py
│   │   │   │   ├── salary_structure_dashboard.py
│   │   │   │   ├── salary_structure_list.js
│   │   │   │   └── test_salary_structure.py
│   │   │   ├── salary_structure_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_structure_assignment.js
│   │   │   │   ├── salary_structure_assignment.json
│   │   │   │   ├── salary_structure_assignment.py
│   │   │   │   └── test_salary_structure_assignment.py
│   │   │   ├── salary_withholding/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_withholding.js
│   │   │   │   ├── salary_withholding.json
│   │   │   │   ├── salary_withholding.py
│   │   │   │   └── test_salary_withholding.py
│   │   │   ├── salary_withholding_cycle/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_withholding_cycle.json
│   │   │   │   └── salary_withholding_cycle.py
│   │   │   └── taxable_salary_slab/
│   │   │       ├── __init__.py
│   │   │       ├── taxable_salary_slab.json
│   │   │       └── taxable_salary_slab.py
│   │   ├── notification/
│   │   │   ├── as
│   │   │   └── retention_bonus/
│   │   │       ├── __init__.py
│   │   │       ├── retention_bonus.json
│   │   │       ├── retention_bonus.md
│   │   │       └── retention_bonus.py
│   │   ├── number_card/
│   │   │   ├── total_declaration_submitted/
│   │   │   │   └── total_declaration_submitted.json
│   │   │   ├── total_incentive_given(last_month)/
│   │   │   │   └── total_incentive_given(last_month).json
│   │   │   ├── total_outgoing_salary(last_month)/
│   │   │   │   └── total_outgoing_salary(last_month).json
│   │   │   └── total_salary_structure/
│   │   │       └── total_salary_structure.json
│   │   ├── payroll_dashboard/
│   │   │   └── payroll/
│   │   │       └── payroll.json
│   │   ├── print_format/
│   │   │   ├── __init__.py
│   │   │   ├── salary_slip_based_on_timesheet/
│   │   │   │   ├── __init__.py
│   │   │   │   └── salary_slip_based_on_timesheet.json
│   │   │   ├── salary_slip_standard/
│   │   │   │   ├── __init__.py
│   │   │   │   └── salary_slip_standard.json
│   │   │   └── salary_slip_with_year_to_date/
│   │   │       ├── __init__.py
│   │   │       └── salary_slip_with_year_to_date.json
│   │   ├── report/
│   │   │   ├── __init__.py
│   │   │   ├── accrued_earnings_report/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── accrued_earnings_report.js
│   │   │   │   ├── accrued_earnings_report.json
│   │   │   │   └── accrued_earnings_report.py
│   │   │   ├── bank_remittance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── bank_remittance.js
│   │   │   │   ├── bank_remittance.json
│   │   │   │   └── bank_remittance.py
│   │   │   ├── income_tax_computation/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_computation.js
│   │   │   │   ├── income_tax_computation.json
│   │   │   │   ├── income_tax_computation.py
│   │   │   │   └── test_income_tax_computation.py
│   │   │   ├── income_tax_deductions/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_deductions.js
│   │   │   │   ├── income_tax_deductions.json
│   │   │   │   ├── income_tax_deductions.py
│   │   │   │   └── test_income_tax_deductions.py
│   │   │   ├── professional_tax_deductions/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── professional_tax_deductions.js
│   │   │   │   ├── professional_tax_deductions.json
│   │   │   │   └── professional_tax_deductions.py
│   │   │   ├── provident_fund_deductions/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── provident_fund_deductions.js
│   │   │   │   ├── provident_fund_deductions.json
│   │   │   │   └── provident_fund_deductions.py
│   │   │   ├── salary_payments_based_on_payment_mode/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_payments_based_on_payment_mode.js
│   │   │   │   ├── salary_payments_based_on_payment_mode.json
│   │   │   │   └── salary_payments_based_on_payment_mode.py
│   │   │   ├── salary_payments_via_ecs/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_payments_via_ecs.js
│   │   │   │   ├── salary_payments_via_ecs.json
│   │   │   │   └── salary_payments_via_ecs.py
│   │   │   └── salary_register/
│   │   │       ├── __init__.py
│   │   │       ├── salary_register.html
│   │   │       ├── salary_register.js
│   │   │       ├── salary_register.json
│   │   │       └── salary_register.py
│   │   ├── utils.py
│   │   └── workspace/
│   │       ├── payroll/
│   │       │   └── payroll.json
│   │       └── tax_&_benefits/
│   │           └── tax_&_benefits.json
│   ├── public/
│   │   ├── .gitkeep
│   │   ├── build.json
│   │   ├── js/
│   │   │   ├── erpnext/
│   │   │   │   ├── bank_transaction.js
│   │   │   │   ├── company.js
│   │   │   │   ├── delivery_trip.js
│   │   │   │   ├── department.js
│   │   │   │   ├── employee.js
│   │   │   │   ├── journal_entry.js
│   │   │   │   ├── payment_entry.js
│   │   │   │   └── timesheet.js
│   │   │   ├── hierarchy-chart.bundle.js
│   │   │   ├── hierarchy_chart/
│   │   │   │   ├── hierarchy_chart_desktop.js
│   │   │   │   └── hierarchy_chart_mobile.js
│   │   │   ├── hrms.bundle.js
│   │   │   ├── interview.bundle.js
│   │   │   ├── performance/
│   │   │   │   └── performance_feedback.js
│   │   │   ├── performance.bundle.js
│   │   │   ├── salary_slip_deductions_report_filters.js
│   │   │   ├── templates/
│   │   │   │   ├── circular_progress_bar.html
│   │   │   │   ├── employees_with_unmarked_attendance.html
│   │   │   │   ├── feedback_history.html
│   │   │   │   ├── feedback_summary.html
│   │   │   │   ├── interview_feedback.html
│   │   │   │   ├── node_card.html
│   │   │   │   ├── performance_feedback.html
│   │   │   │   └── rating.html
│   │   │   └── utils/
│   │   │       ├── index.js
│   │   │       ├── leave_utils.js
│   │   │       └── payroll_utils.js
│   │   └── scss/
│   │       ├── circular_progress.scss
│   │       ├── feedback.scss
│   │       ├── hierarchy_chart.scss
│   │       └── hrms.bundle.scss
│   ├── regional/
│   │   ├── india/
│   │   │   ├── data/
│   │   │   │   └── salary_components.json
│   │   │   ├── setup.py
│   │   │   └── utils.py
│   │   └── united_arab_emirates/
│   │       └── setup.py
│   ├── setup.py
│   ├── subscription_utils.py
│   ├── templates/
│   │   ├── __init__.py
│   │   ├── emails/
│   │   │   ├── anniversary_reminder.html
│   │   │   ├── birthday_reminder.html
│   │   │   ├── daily_work_summary.html
│   │   │   ├── daily_work_summary.txt
│   │   │   ├── holiday_reminder.html
│   │   │   └── training_event.html
│   │   ├── generators/
│   │   │   └── job_opening.html
│   │   ├── includes/
│   │   │   └── salary_slip_log.html
│   │   └── pages/
│   │       └── __init__.py
│   ├── tests/
│   │   ├── test_utils.py
│   │   └── utils.py
│   ├── uninstall.py
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── custom_method_for_charts.py
│   │   ├── hierarchy_chart.py
│   │   └── holiday_list.py
│   ├── workspace_sidebar/
│   │   ├── expenses.json
│   │   ├── leaves.json
│   │   ├── payroll.json
│   │   ├── people.json
│   │   ├── performance.json
│   │   ├── recruitment.json
│   │   ├── shift_&_attendance.json
│   │   ├── tax_&_benefits.json
│   │   └── tenure.json
│   └── www/
│       ├── __init__.py
│       ├── hrms.py
│       ├── jobs/
│       │   ├── __init__.py
│       │   ├── index.css
│       │   ├── index.html
│       │   ├── index.js
│       │   └── index.py
│       └── roster.py
├── license.txt
├── package.json
├── pyproject.toml
└── roster/
    ├── .gitignore
    ├── index.d.ts
    ├── index.html
    ├── package.json
    ├── postcss.config.js
    ├── src/
    │   ├── App.vue
    │   ├── components/
    │   │   ├── Link.vue
    │   │   ├── MonthViewHeader.vue
    │   │   ├── MonthViewTable.vue
    │   │   ├── NavBar.vue
    │   │   └── ShiftAssignmentDialog.vue
    │   ├── icons/
    │   │   └── FrappeHRLogo.vue
    │   ├── index.css
    │   ├── main.ts
    │   ├── router.ts
    │   ├── utils/
    │   │   ├── dayjs.ts
    │   │   └── index.ts
    │   └── views/
    │       ├── Home.vue
    │       └── MonthView.vue
    ├── tailwind.config.js
    ├── tsconfig.json
    └── vite.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# Root editor config file
root = true

# Common settings
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8

# js indentation settings
[{*.js,*.ts,*.vue,*.css,*.scss,*.html}]
indent_style = tab
indent_size = 4
max_line_length = 99


================================================
FILE: .git-blame-ignore-revs
================================================
# Since version 2.23 (released in August 2019), git-blame has a feature
# to ignore or bypass certain commits.
#
# This file contains a list of commits that are not likely what you
# are looking for in a blame, such as mass reformatting or renaming.
# You can set this file as a default ignore file for blame by running
# the following command.
#
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs

# sort and cleanup imports
4872c156974291f0c4c88f26033fef0b900ca995

# old black formatting commit (from erpnext)
76c895a6c659356151433715a1efe9337e348c11

# bulk formatting
b55d6e27af6bd274dfa47e66a3012ddec68ce798

# bulk formatting PWA frontend code
f37f15b2b5329e3b0b35891e1c4fd82f48562c6d

# bulk formatting PWA frontend code
920daa1a3ddccaefaf7b9348f850831d6e0a0e6b

# python ruff formatting
b68457552bb3540565267f23fbfcee35c9f86e1c

# js, scss prettier formatting
1ab1d6238171a5cee3263812402a8b82e7131cb1

================================================
FILE: .github/CODEOWNERS
================================================
# This is a comment.
# Each line is a file pattern followed by one or more owners.

# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence.

hrms/hr/ @ruchamahabal @asmitahase
hrms/payroll/ @ruchamahabal @AyshaHakeem @iamraheelkhan

frontend/ @ruchamahabal @asmitahase
roster/ @ruchamahabal @asmitahase

.github/ @asmitahase


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
---
name: Bug Report
description: Report a bug encountered while using Frappe HR
labels: ["bug"]

body:
  - type: markdown
    attributes:
      value: |
        Welcome to Frappe HR issue tracker! Before creating an issue, please consider the following:

        1. This tracker should only be used to report bugs and request features / enhancements to Frappe HR
            - For questions and general support, checkout the [documentation](https://frappehr.com/docs) or use the [forum](https://discuss.frappe.io) to get inputs from the open source community.
            - For documentation issues, propose edit on the [documentation site](https://frappehr.com/docs) directly.
        2. When making a bug report, make sure you provide all required information. The easier it is for
           maintainers to reproduce, the faster it'll be fixed.
        3. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉

  - type: textarea
    id: bug-info
    attributes:
      label: Information about bug
      description: Also tell us, what did you expect to happen? If applicable, add screenshots to help explain your problem.
      placeholder: Please provide as much information as possible.
    validations:
      required: true

  - type: dropdown
    id: module
    attributes:
      label: Module
      description: Select the affected module of Frappe HR.
      multiple: true
      options:
        - HR
        - Payroll
        - other
    validations:
      required: true

  - type: textarea
    id: exact-version
    attributes:
      label: Version
      description: Share exact version number of Frappe, ERPNext and Frappe HR you are using.
      placeholder: |
        Frappe version -
        ERPNext version -
        Frappe HR version -
    validations:
      required: true

  - type: dropdown
    id: install-method
    attributes:
      label: Installation method
      options:
        - docker
        - easy-install
        - manual install
        - FrappeCloud
    validations:
      required: false

  - type: textarea
    id: logs
    attributes:
      label: Relevant log output / Stack trace / Full Error Message.
      description: Please copy and paste any relevant log output. This will be automatically formatted.
      render: shell

  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: |
        By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/frappe/hrms/blob/develop/CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true
...


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Community Forum
    url: https://discuss.frappe.io/
    about: For general QnA, discussions and community help.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yaml
================================================
---
name: Feature Request
description: Suggest an idea to improve Frappe HR
labels: ["feature-request"]

body:
  - type: markdown
    attributes:
      value: |
        Welcome to Frappe HR issue tracker! Before submitting a request, please consider the following:

        1. This tracker should only be used to report bugs and request features / enhancements to Frappe HR
            - For questions and general support, checkout the [documentation](https://docs.frappe.io/hr) or use the [forum](https://discuss.frappe.io) to get inputs from the open source community.
        2. Use the search function before creating a new issue. Duplicates will be closed and directed to
          the original discussion.
        3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.


        Please keep in mind that we get many many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.

        If you're in urgent need to a feature, please try the following channels to get paid developments done quickly:
        1. Certified Frappe partners: https://frappe.io/partners
        2. Developer community on Frappe forums: https://discuss.frappe.io/c/developers/5
        3. Telegram group for Frappe HR development work: https://t.me/frappehr

  - type: textarea
    id: problem-info
    attributes:
      label: Is your feature request related to a problem? Please describe.
      description: A clear and concise description of what the problem is. Eg. I'm always frustrated when [...]
      placeholder: Please provide as much information as possible.

  - type: textarea
    id: solution-info
    attributes:
      label: Describe the solution you'd like
      description: A clear and concise description of what you want to happen.

  - type: textarea
    id: alternatives-info
    attributes:
      label: Describe the alternatives you've considered
      description: A clear and concise description of any alternative solutions or features you've considered.

  - type: textarea
    id: additional-info
    attributes:
      label: Additional context
      description: Add any other context or screenshots about the feature request here.
...


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--

Some key notes before you open a PR:

 1. Select which branch should this PR be merged in?
 2. PR name follows [convention](http://karma-runner.github.io/4.0/dev/git-commit-msg.html)
 3. All tests pass locally, UI and Unit tests
 4. All business logic and validations must be on the server-side
 5. Update necessary Documentation
 6. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes


Also, if you're new here

- Documentation Guidelines => https://github.com/frappe/erpnext/wiki/Page-format-for-ERPNext-docs

- Contribution Guide => https://github.com/frappe/erpnext/wiki/Contribution-Guidelines

- Pull Request Checklist => https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist

-->

> Please provide enough information so that others can review your pull request:

<!-- You can skip this if you're fixing a typo or updating existing documentation -->

> Explain the **details** for making this change. What existing problem does the pull request solve?

<!-- Example: When "Adding a function to do X", explain why it is necessary to have a way to do X. -->

> Screenshots/GIFs

<!-- Add images/recordings to better visualize the change: expected/current behviour -->


================================================
FILE: .github/helper/apps.json
================================================
[
    {"url": "https://github.com/frappe/erpnext","branch": "version-15"},
    {"url": "https://github.com/frappe/payments","branch": "version-15"},
    {"url": "https://github.com/frappe/hrms","branch": "version-15"}
]

================================================
FILE: .github/helper/documentation.py
================================================
import sys
import requests
from urllib.parse import urlparse


def uri_validator(x):
	result = urlparse(x)
	return all([result.scheme, result.netloc, result.path])


def docs_link_exists(body):
	for line in body.splitlines():
		for word in line.split():
			if word.startswith("http") and uri_validator(word):
				parsed_url = urlparse(word)
				if parsed_url.netloc == "github.com":
					parts = parsed_url.path.split("/")
					if len(parts) == 5 and parts[1] == "frappe" and parts[2] == "hrms":
						return True
				elif parsed_url.netloc == "docs.frappe.io":
					return True


if __name__ == "__main__":
	pr = sys.argv[1]
	response = requests.get("https://api.github.com/repos/frappe/hrms/pulls/{}".format(pr))

	if response.ok:
		payload = response.json()
		title = (payload.get("title") or "").lower().strip()
		head_sha = (payload.get("head") or {}).get("sha")
		body = (payload.get("body") or "").lower()

		if title.startswith("feat") and head_sha and "no-docs" not in body and "backport" not in body:
			if docs_link_exists(body):
				print("Documentation Link Found. You're Awesome! 🎉")

			else:
				print("Documentation Link Not Found! ⚠️")
				sys.exit(1)

		else:
			print("Skipping documentation checks... 🏃")


================================================
FILE: .github/helper/install.sh
================================================
#!/bin/bash

set -e

cd ~ || exit

sudo apt update
sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client libmariadb-dev

pip install frappe-bench

githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
frappeuser=${FRAPPE_USER:-"frappe"}
frappebranch=${FRAPPE_BRANCH:-$githubbranch}
erpnextbranch=${ERPNEXT_BRANCH:-$githubbranch}
paymentsbranch=${PAYMENTS_BRANCH:-${githubbranch%"-hotfix"}}
lendingbranch="develop"

git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench

mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/

mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"

mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"

mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"

install_whktml() {
    wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
    tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
    sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
    sudo chmod o+x /usr/local/bin/wkhtmltopdf
}
install_whktml &

cd ~/frappe-bench || exit

sed -i 's/watch:/# watch:/g' Procfile
sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile

bench get-app "https://github.com/${frappeuser}/payments" --branch "$paymentsbranch"
bench get-app "https://github.com/${frappeuser}/erpnext" --branch "$erpnextbranch" --resolve-deps
bench get-app "https://github.com/${frappeuser}/lending" --branch "$lendingbranch"
bench get-app hrms "${GITHUB_WORKSPACE}"
bench setup requirements --dev

bench start &>> ~/frappe-bench/bench_start.log &
CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes

bench --verbose --site test_site install-app lending
bench --verbose --site test_site install-app hrms


================================================
FILE: .github/helper/site_config.json
================================================
{
    "db_host": "127.0.0.1",
    "db_port": 3306,
    "db_name": "test_frappe",
    "db_password": "test_frappe",
    "use_mysqlclient": 1,
    "auto_email_id": "test@example.com",
    "mail_server": "smtp.example.com",
    "mail_login": "test@example.com",
    "mail_password": "test",
    "admin_password": "admin",
    "root_login": "root",
    "root_password": "root",
    "host_name": "http://test_site:8000",
    "install_apps": ["payments", "erpnext"],
    "throttle_user_limit": 100
}


================================================
FILE: .github/helper/translation.py
================================================
import re
import sys

errors_encounter = 0
pattern = re.compile(
	r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)"
)
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
f_string_pattern = re.compile(r"_\(f[\"']")
starts_with_f_pattern = re.compile(r"_\(f")

# skip first argument
files = sys.argv[1:]
files_to_scan = [_file for _file in files if _file.endswith((".py", ".js"))]

for _file in files_to_scan:
	with open(_file, "r") as f:
		print(f"Checking: {_file}")
		file_lines = f.readlines()
		for line_number, line in enumerate(file_lines, 1):
			if "frappe-lint: disable-translate" in line:
				continue

			start_matches = start_pattern.search(line)
			if start_matches:
				starts_with_f = starts_with_f_pattern.search(line)

				if starts_with_f:
					has_f_string = f_string_pattern.search(line)
					if has_f_string:
						errors_encounter += 1
						print(
							f"\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}"
						)
						continue
					else:
						continue

				match = pattern.search(line)
				error_found = False

				if not match and line.endswith((",\n", "[\n")):
					# concat remaining text to validate multiline pattern
					line = "".join(file_lines[line_number - 1 :])
					line = line[start_matches.start() + 1 :]
					match = pattern.match(line)

				if not match:
					error_found = True
					print(f"\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}")

				if not error_found and not words_pattern.search(line):
					error_found = True
					print(
						f"\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}"
					)

				if error_found:
					errors_encounter += 1

if errors_encounter > 0:
	print(
		'\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.'
	)
	sys.exit(1)
else:
	print("\nGood To Go!")


================================================
FILE: .github/helper/update_pot_file.sh
================================================
#!/bin/bash
set -e
cd ~ || exit

echo "Setting Up Bench..."

pip install frappe-bench
bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)" --frappe-branch "${BASE_BRANCH}"
cd ./frappe-bench || exit

# We want to exclude strings from ERPNext from HRMS's translations.
echo "Get ERPNext..."
bench get-app --skip-assets --branch "${BASE_BRANCH}" erpnext

echo "Get HRMS..."
bench get-app --skip-assets hrms "${GITHUB_WORKSPACE}"

echo "Generating POT file..."
bench generate-pot-file --app hrms

cd ./apps/hrms || exit

echo "Configuring git user..."
git config user.email "developers@erpnext.com"
git config user.name "frappe-pr-bot"

echo "Setting the correct git remote..."
# Here, the git remote is a local file path by default. Let's change it to the upstream repo.
git remote set-url upstream https://github.com/frappe/hrms.git

echo "Creating a new branch..."
isodate=$(date -u +"%Y-%m-%d")
branch_name="pot_${BASE_BRANCH}_${isodate}"
git checkout -b "${branch_name}"

echo "Commiting changes..."
git add hrms/locale/main.pot
git commit -m "chore: update POT file"

gh auth setup-git
git push -u upstream "${branch_name}"

echo "Creating a PR..."
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R frappe/hrms


================================================
FILE: .github/labeler.yml
================================================
# Any python files modifed but no test files modified
needs-tests:
- any: ['hrms/**/*.py']
  all: ['!hrms/**/test*.py']


================================================
FILE: .github/release.yml
================================================
changelog:
  exclude:
    labels:
      - skip-release-notes


================================================
FILE: .github/workflows/build_image.yml
================================================
name: Build Container Image
on:
  release:
    types: [published]
  workflow_dispatch:
  push:
    branches:
      - version-15
    tags:
      - "*"
jobs:
  build:
    name: Build
    runs-on: ubuntu-latest

    strategy:
      matrix:
        arch: [amd64, arm64]

    permissions:
      packages: write

    steps:
      - name: Checkout Entire Repository
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          platforms: linux/${{ matrix.arch }}
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
          
      - name: Set Branch
        run: |
          export APPS_JSON_PATH='${{ github.workspace }}/.github/helper/apps.json'
          echo "APPS_JSON_BASE64=$(cat $APPS_JSON_PATH | base64 -w 0)" >> $GITHUB_ENV
          echo "FRAPPE_BRANCH=version-15" >> $GITHUB_ENV

      - name: Set Image Tag
        run: |
          echo "IMAGE_TAG=stable" >> $GITHUB_ENV
      - uses: actions/checkout@v4
        with:
          repository: frappe/frappe_docker
          path: builds

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          push: true
          context: builds
          file: builds/images/layered/Containerfile
          tags: >
            ghcr.io/${{ github.repository }}:${{ github.ref_name }},
            ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }}
          build-args: |
            "FRAPPE_BRANCH=${{ env.FRAPPE_BRANCH }}"
            "APPS_JSON_BASE64=${{ env.APPS_JSON_BASE64 }}"

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  pull_request:
    paths-ignore:
      - "**.css"
      - "**.js"
      - "**.md"
      - "**.html"
      - "**.csv"
      - "**.po"
      - "**.pot"
  schedule:
    # Run everday at midnight UTC / 5:30 IST
    - cron: "0 0 * * *"
env:
    HR_BRANCH: ${{ github.base_ref || github.ref_name }}

concurrency:
  group: develop-${{ github.event.number }}
  cancel-in-progress: true

jobs:
  tests:
    runs-on: ubuntu-latest
    timeout-minutes: 60
    env:
      NODE_ENV: "production"
      WITH_COVERAGE: ${{ github.event_name != 'pull_request' }}

    strategy:
      fail-fast: false

      matrix:
        container: [1, 2]

    name: Python Unit Tests

    services:
      mysql:
        image: mariadb:11.8
        env:
          MARIADB_ROOT_PASSWORD: 'root'
        ports:
          - 3306:3306
        options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3

    steps:
      - name: Clone
        uses: actions/checkout@v6

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.14'

      - name: Check for valid Python & Merge Conflicts
        run: |
          python -m compileall -f "${GITHUB_WORKSPACE}"
          if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
              then echo "Found merge conflicts"
              exit 1
          fi

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24
          check-latest: true

      - name: Add to Hosts
        run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts

      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
          restore-keys: |
            ${{ runner.os }}-pip-
            ${{ runner.os }}-

      - name: Cache node modules
        uses: actions/cache@v4
        env:
          cache-name: cache-node-modules
        with:
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-

      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - uses: actions/cache@v4
        id: yarn-cache
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - name: Install
        run: |
          bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
        env:
          FRAPPE_USER: ${{ github.event.inputs.user }}
          FRAPPE_BRANCH: ${{ github.event.inputs.branch }}

      - name: Run Tests
        run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app hrms --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }} --lightmode
        env:
          TYPE: server
          CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }}

      - name: Upload coverage data
        uses: actions/upload-artifact@v4
        if: github.event_name != 'pull_request'
        with:
          name: coverage-${{ matrix.container }}
          path: /home/runner/frappe-bench/sites/coverage.xml

  coverage:
    name: Coverage Wrap Up
    needs: tests
    runs-on: ubuntu-latest
    if: ${{ github.event_name != 'pull_request' }}
    steps:
      - name: Clone
        uses: actions/checkout@v6

      - name: Download artifacts
        uses: actions/download-artifact@v4

      - name: Upload coverage data
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: true
          verbose: true


================================================
FILE: .github/workflows/docs_checker.yml
================================================
name: 'Documentation Required'
on:
  pull_request:
    types: [ opened, synchronize, reopened, edited ]

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: 'Setup Environment'
        uses: actions/setup-python@v6
        with:
          python-version: 3.8

      - name: 'Clone repo'
        uses: actions/checkout@v6

      - name: Validate Docs
        env:
          PR_NUMBER: ${{ github.event.number }}
        run: |
          pip install requests --quiet
          python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER

================================================
FILE: .github/workflows/generate-pot-file.yml
================================================
name: Regenerate POT file (translatable strings)
on:
  schedule:
    # 9:30 UTC => 3 PM IST Sunday
    - cron: "30 9 * * 0"
  workflow_dispatch:

jobs:
  regenerate-pot-file:
    name: Regenerate POT file
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        branch: ["develop"]
    permissions:
      contents: write

    steps:
        - name: Checkout
          uses: actions/checkout@v6
          with:
            ref: ${{ matrix.branch }}

        - name: Setup Python
          uses: actions/setup-python@v6
          with:
            python-version: "3.14"

        - name: Setup Node
          uses: actions/setup-node@v6
          with:
            node-version: 24

        - name: Run script to update POT file
          run: |
            bash ${GITHUB_WORKSPACE}/.github/helper/update_pot_file.sh
          env:
            GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
            BASE_BRANCH: ${{ matrix.branch }}


================================================
FILE: .github/workflows/initiate_release.yml
================================================
# This workflow is agnostic to branches. Only maintain on develop branch.
# To add/remove versions just modify the matrix.

name: Create weekly release pull requests
on:
  schedule:
    # 9:45 UTC => 3:15 PM IST Tuesday
    - cron: "45 9 * * 2"
  workflow_dispatch:

jobs:
  stable-release:
    name: Release
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        version: ["14", "15", "16"]

    steps:
      - uses: octokit/request-action@v2.x
        with:
          route: POST /repos/{owner}/{repo}/pulls
          owner: frappe
          repo: hrms
          title: |-
            "chore: release v${{ matrix.version }}"
          body: "Automated Release."
          base: version-${{ matrix.version }}
          head: version-${{ matrix.version }}-hotfix
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}


================================================
FILE: .github/workflows/labeller.yml
================================================
name: "Pull Request Labeler"
on:
  pull_request_target:
    types: [opened, reopened]

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/labeler@v4
      with:
        repo-token: "${{ secrets.GITHUB_TOKEN }}"


================================================
FILE: .github/workflows/linters.yml
================================================
name: Linters

on:
  pull_request: { }

jobs:
  commit-lint:
    name: 'Semantic Commits'
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'

    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 200
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          check-latest: true

      - name: Check commit titles
        run: |
          npm install @commitlint/cli @commitlint/config-conventional
          npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}

  linter:
    name: 'Frappe Linter'
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'

    steps:
      - uses: actions/checkout@v6

      - name: Set up Python 3.14
        uses: actions/setup-python@v6
        with:
          python-version: '3.14'

      - name: Install and Run Pre-commit
        uses: pre-commit/action@v3.0.0

      - name: Download Semgrep rules
        run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules

      - name: Download semgrep
        run: pip install semgrep

      - name: Run Semgrep rules
        run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

================================================
FILE: .github/workflows/on_release.yml
================================================
name: Generate Semantic Release
on:
  workflow_dispatch:
  push:
    branches:
      - version-14
jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Entire Repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 20
      - name: Setup dependencies
        run: |
          npm install @semantic-release/git @semantic-release/exec --no-save
      - name: Create Release
        env:
          GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
          GIT_AUTHOR_NAME: "Frappe PR Bot"
          GIT_AUTHOR_EMAIL: "developers@frappe.io"
          GIT_COMMITTER_NAME: "Frappe PR Bot"
          GIT_COMMITTER_EMAIL: "developers@frappe.io"
        run: npx semantic-release


================================================
FILE: .github/workflows/release_notes.yml
================================================
# This action:
#
# 1. Generates release notes using github API.
# 2. Strips unnecessary info like chore/style etc from notes.
# 3. Updates release info.

# This action needs to be maintained on all branches that do releases.

name: 'Release Notes'

on:
  workflow_dispatch:
    inputs:
      tag_name:
        description: 'Tag of release like v13.0.0'
        required: true
        type: string
  release:
    types: [released]

permissions:
  contents: read

jobs:
  regen-notes:
    name: 'Regenerate release notes'
    runs-on: ubuntu-latest

    steps:
    - name: Update notes
      run: |
        NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json"  /repos/frappe/hrms/releases/generate-notes  -f tag_name=$RELEASE_TAG \
          | jq -r '.body' \
          | sed -E '/^\* (chore|ci|test|docs|style)/d' \
          | sed -E 's/by @mergify //'
        )
        RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/hrms/releases/tags/$RELEASE_TAG | jq -r '.id')
        gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/hrms/releases/$RELEASE_ID -f body="$NEW_NOTES"

      env:
        GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
        RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}

================================================
FILE: .github/workflows/run-individual-tests.yml
================================================
name: Individual

on:
  workflow_dispatch:

concurrency:
  group: server-individual-tests-lightmode-develop
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  discover:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
    - name: Clone
      uses: actions/checkout@v6
    - id: set-matrix
      run: |
        # Use grep and find to get the list of test files
        matrix=$(find . -path '*/test_*.py' | xargs grep -l 'def test_' | sort | awk '{
            # Remove ./ prefix, file extension, and replace / with .
            gsub(/^\.\//, "", $0)
            gsub(/\.py$/, "", $0)
            gsub(/\//, ".", $0)
            # Add to array
            tests[NR] = $0
        }
        END {
            # Start JSON array
            printf "{\n  \"include\": [\n"
            # Loop through array and create JSON objects
            for (i=1; i<=NR; i++) {
                printf "    {\"test\": \"%s\"}", tests[i]
                if (i < NR) printf ","
                printf "\n"
            }
            # Close JSON array
            printf "  ]\n}"
        }')

        # Output the matrix
        echo "matrix=$(echo "$matrix" | jq -c)" >> $GITHUB_OUTPUT

        # For debugging (optional)
        echo "Generated matrix:"
        echo "$matrix"
  test:
    needs: discover
    runs-on: ubuntu-latest
    timeout-minutes: 60
    env:
      NODE_ENV: "production"

    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.discover.outputs.matrix)}}
      # max-parallel: 10

    name: Test

    services:
      mysql:
        image: mariadb:10.6
        env:
          MARIADB_ROOT_PASSWORD: 'root'
        ports:
          - 3306:3306
        options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3

    steps:
      - name: Clone
        uses: actions/checkout@v6

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.14'

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24
          check-latest: true

      - name: Add to Hosts
        run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts

      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
          restore-keys: |
            ${{ runner.os }}-pip-
            ${{ runner.os }}-

      - name: Cache node modules
        uses: actions/cache@v4
        env:
          cache-name: cache-node-modules
        with:
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-

      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v4
        id: yarn-cache
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - name: Install
        run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
        env:
          DB: mariadb
          TYPE: server
          FRAPPE_USER: ${{ github.event.inputs.user }}
          FRAPPE_BRANCH: ${{ github.event.inputs.branch }}

      - name: Run Tests
        run: |
          site_name=$(echo "${{matrix.test}}" | sed -e 's/.*\.\(test_.*$\)/\1/')
          echo "$site_name"
          mkdir ~/frappe-bench/sites/$site_name
          cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/$site_name/site_config.json
          cd ~/frappe-bench/
          bench --site $site_name reinstall --yes
          bench --site $site_name install-app hrms
          bench --site $site_name set-config allow_tests true
          bench --site $site_name run-tests --module ${{ matrix.test }} --lightmode



================================================
FILE: .github/workflows/stale.yml
================================================
# https://github.com/actions/stale

name: "Close Stale PRs"
on:
  schedule:
    - cron: 0 0 * * *
  workflow_dispatch:

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v9
        with:
          days-before-pr-stale: 15
          days-before-pr-close: 3
          stale-pr-label: 'Inactive'
          exempt-draft-pr: true
          days-before-issue-stale: 5
          days-before-issue-close: 2
          stale-issue-label: 'Inactive'
          any-of-issue-labels: "question,can't replicate"
          remove-issue-stale-when-updated: true
          labels-to-remove-when-unstale: 'Inactive'
          stale-pr-message: |
            This pull request is being marked as inactive because of no recent activity. 
            If your PR hasn't been reviewed, it's likely because it doesn't fullfill the [contribution guidelines](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines). Please read them carefully and fix the pull request. When you are sure all items are checked, please ping relevant codeowner in the comment. Be nice, they have a lot on their plate too.
            
            It will be closed in 3 days if no further activity occurs.
            Thank you for contributing!
          stale-issue-message: |
            Hi, this is your friendly neighbourhood bot :)
            
            Thank you for taking time to report the issue, however your description of the issue is insufficient to understand the exact problem and/or to replicate and fix it. Please provide the information requested by the maintainers, so this can be fixed. More the steps/screenshots/videos the better.

            It will be closed in 2 days if no further activity occurs.
            Beep Boop!


================================================
FILE: .gitignore
================================================
.DS_Store
*.pyc
*.egg-info
*.swp
tags
hrms/public/dist
hrms/public/node_modules
hrms/docs/current
node_modules/
dist/
__pycache__/

# build/
.vscode
.vs
node_modules
*debug.log
hrms/docs/current
hrms/public/frontend
hrms/www/hrms.html
hrms/public/roster
hrms/www/roster.html


================================================
FILE: .gitmodules
================================================
[submodule "frappe-ui"]
	path = frappe-ui
	url = https://github.com/frappe/frappe-ui


================================================
FILE: .mergify.yml
================================================
pull_request_rules:
  - name: Auto-close PRs on stable branch
    conditions:
      - and:
        - and:
          - author!=ruchamahabal
          - author!=asmitahase
          - author!=frappe-pr-bot
          - author!=mergify[bot]
        - or:
          - base=version-16
          - base=version-15
          - base=version-14
    actions:
      close:
      comment:
          message: |
            @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch or the develop branch.

  - name: Automatic merge on CI success and review
    conditions:
      - status-success=linters
      - status-success=Sider
      - status-success=Semantic Pull Request
      - status-success=Python Unit Tests (1)
      - status-success=Python Unit Tests (2)
      - label!=dont-merge
      - label!=squash
      - "#approved-reviews-by>=1"
    actions:
      merge:
        method: merge

  - name: Automatic squash on CI success and review
    conditions:
      - status-success=linters
      - status-success=Sider
      - status-success=Python Unit Tests (1)
      - status-success=Python Unit Tests (2)
      - label!=dont-merge
      - label=squash
      - "#approved-reviews-by>=1"
    actions:
      merge:
        method: squash
        commit_message_template: |
            {{ title }} (#{{ number }})

            {{ body }}

  - name: backport to develop
    conditions:
      - label="backport develop"
    actions:
      backport:
        branches:
          - develop
        assignees:
          - "{{ author }}"

  - name: backport to version-14-hotfix
    conditions:
      - label="backport version-14-hotfix"
    actions:
      backport:
        branches:
          - version-14-hotfix
        assignees:
          - "{{ author }}"

  - name: backport to version-15-hotfix
    conditions:
      - label="backport version-15-hotfix"
    actions:
      backport:
        branches:
          - version-15-hotfix
        assignees:
          - "{{ author }}"

  - name: backport to version-16-hotfix
    conditions:
      - label="backport version-16-hotfix"
    actions:
      backport:
        branches:
          - version-16-hotfix
        assignees:
          - "{{ author }}"

================================================
FILE: .pre-commit-config.yaml
================================================
exclude: 'node_modules|.git'
default_stages: [commit]
fail_fast: false


repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.0.1
    hooks:
      - id: trailing-whitespace
        files: "hrms.*"
        exclude: ".*json$|.*txt$|.*csv|.*md"
      - id: check-yaml
      - id: no-commit-to-branch
        args: ['--branch', 'develop']
      - id: check-merge-conflict
      - id: check-ast

  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        types_or: [javascript, ts, vue, css, scss]
        # Ignore frontend folder and any files that might contain jinja / bundles
        exclude: |
            (?x)^(
                frontend/.*|
                hrms/public/dist/.*|
                .*node_modules.*|
                .*boilerplate.*|
                hrms/templates/includes/.*|
                hrms/hr/doctype/employee_promotion/employee_promotion.js|
                hrms/hr/doctype/employee_transfer/employee_transfer.js|
            )$

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.3.7
    hooks:
      - id: ruff
        name: "Run ruff linter and apply fixes"
        args: ["--fix"]

      - id: ruff-format
        name: "Format Python code"

ci:
    autoupdate_schedule: weekly
    skip: []
    submodules: false


================================================
FILE: .releaserc
================================================
{
	"branches": ["version-14"],
	"plugins": [
		"@semantic-release/commit-analyzer", {
			"preset": "angular",
			"releaseRules": [
				{"breaking": true, "release": false}
			]
		},
		"@semantic-release/release-notes-generator",
		[
			"@semantic-release/exec", {
				"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" hrms/__init__.py'
			}
		],
		[
			"@semantic-release/git", {
				"assets": ["hrms/__init__.py"],
				"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
			}
		],
		"@semantic-release/github"
	]
}

================================================
FILE: .semgrepignore
================================================
hrms/patches/post_install/


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@frappe.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: MANIFEST.in
================================================
include MANIFEST.in
include *.json
include *.md
include *.py
include *.txt
recursive-include hrms *.css
recursive-include hrms *.csv
recursive-include hrms *.html
recursive-include hrms *.ico
recursive-include hrms *.js
recursive-include hrms *.json
recursive-include hrms *.md
recursive-include hrms *.png
recursive-include hrms *.py
recursive-include hrms *.svg
recursive-include hrms *.txt
recursive-exclude hrms *.pyc


================================================
FILE: README.md
================================================
<div align="center">
	<a href="https://frappe.io/hr">
		<img src=".github/frappe-hr-logo.png" height="80px" width="80px" alt="Frappe HR Logo">
	</a>
	<h2>Frappe HR</h2>
	<p align="center">
		<p>Open Source, modern, and easy-to-use HR and Payroll Software</p>
	</p>

[![CI](https://github.com/frappe/hrms/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/frappe/hrms/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/frappe/hrms/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/hrms)

<a href="https://trendshift.io/repositories/10972" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10972" alt="frappe%2Fhrms | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>

<div align="center">
	<img src=".github/hrms-hero.png"/>
</div>

<div align="center">
	<a href="https://frappe.io/hr">Website</a>
	-
	<a href="https://docs.frappe.io/hr/introduction">Documentation</a>
</div>

## Frappe HR

Frappe HR has everything you need to drive excellence within the company. It's a complete HRMS solution with over 13 different modules right from Employee Management, Onboarding, Leaves, to Payroll, Taxation, and more!

## Motivation
When Frappe team started growing in terms of size, we needed an open-source HR and Payroll software. We didn't find any "true" open-source HR software out there and so decided to build one ourselves.
Initially, it was a set of modules within ERPNext but version 14 onwards, as the modules became more mature, Frappe HR was created as a separate product.

## Key Features

- **Employee Lifecycle**: From onboarding employees, managing promotions and transfers, all the way to documenting feedback with exit interviews, make life easier for employees throughout their life cycle.
- **Leave and Attendance**: Configure leave policies, pull regional holidays with a click, check-in and check-out with geolocation capturing, track leave balances and attendance with reports.
- **Expense Claims and Advances**: Manage employee advances, claim expenses, configure multi-level approval workflows, all this with seamless integration with ERPNext accounting.
- **Performance Management**: Track goals, align goals with key result areas (KRAs), enable employees to evaluate themselves, make managing appraisal cycles easy.
- **Payroll & Taxation**: Create salary structures, configure income tax slabs, run standard payroll, accommodate additional salaries and off cycle payments, view income breakup on salary slips and so much more.
- **Frappe HR Mobile App**: Apply for and approve leaves on the go, check-in and check-out, access employee profile right from the mobile app.

<details open>

<summary>View Screenshots</summary>
	<img src=".github/hrms-appraisal.png"/>
	<img src=".github/hrms-requisition.png"/>
	<img src=".github/hrms-attendance.png"/>
	<img src=".github/hrms-salary.png"/>
	<img src=".github/hrms-pwa.png"/>
</details>

### Under the Hood

- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework written in Python and Javascript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.

- [**Frappe UI**](https://github.com/frappe/frappe-ui): A Vue-based UI library, to provide a modern user interface. The Frappe UI library provides a variety of components that can be used to build single-page applications on top of the Frappe Framework.

## Production Setup

### Managed Hosting

You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications with peace of mind.

It takes care of installation, setup, upgrades, monitoring, maintenance and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.

<div>
	<a href="https://frappecloud.com/hrms/signup" target="_blank">
		<picture>
			<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
			<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
		</picture>
	</a>
</div>


## Development setup
### Docker
You need Docker, docker-compose and git setup on your machine. Refer [Docker documentation](https://docs.docker.com/). After that, run the following commands:
```
git clone https://github.com/frappe/hrms
cd hrms/docker
docker-compose up
```

Wait for some time until the setup script creates a site. After that you can access `http://localhost:8000` in your browser and the login screen for HR should show up.

Use the following credentials to log in:

- Username: `Administrator`
- Password: `admin`

### Local

1. Set up bench by following the [Installation Steps](https://frappeframework.com/docs/user/en/installation) and start the server and keep it running
	```sh
	$ bench start
	```
2. In a separate terminal window, run the following commands
	```sh
	$ bench new-site hrms.localhost
	$ bench get-app erpnext
	$ bench get-app hrms
	$ bench --site hrms.localhost install-app hrms
	$ bench --site hrms.localhost add-to-hosts
	```
3. You can access the site at `http://hrms.localhost:8080`

## Learning and Community

1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
2. [Documentation](https://docs.frappe.io/hr) - Extensive documentation for Frappe HR.
3. [User Forum](https://discuss.erpnext.com/) - Engage with the community of ERPNext users and service providers.
4. [Telegram Group](https://t.me/frappehr) - Get instant help from the community of users.


## Contributing

1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
1. [Report Security Vulnerabilities](https://erpnext.com/security)
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)


## Logo and Trademark Policy

Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md).

<br />
<br />
<div align="center" style="padding-top: 0.75rem;">
	<a href="https://frappe.io" target="_blank">
		<picture>
			<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/Frappe-white.png">
			<img src="https://frappe.io/files/Frappe-black.png" alt="Frappe Technologies" height="28"/>
		</picture>
	</a>
</div>



================================================
FILE: SECURITY.md
================================================
# Security Policy

The Frappe HR team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).

You can help us make Frappe HR and all its users more secure by following the [Reporting guidelines](https://frappe.io/security).

We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.


================================================
FILE: codecov.yml
================================================
codecov:
  require_ci_to_pass: yes

coverage:
  status:
    project:
      default:
        target: auto
        threshold: 0.5%

    patch:
      default:
        target: 85%
        threshold: 0%
        base: auto
        branches:
          - develop
        if_ci_failed: ignore
        only_pulls: true

comment:
  layout: "diff, files"
  require_changes: true



================================================
FILE: commitlint.config.js
================================================
module.exports = {
	parserPreset: "conventional-changelog-conventionalcommits",
	rules: {
		"subject-empty": [2, "never"],
		"type-case": [2, "always", "lower-case"],
		"type-empty": [2, "never"],
		"type-enum": [
			2,
			"always",
			[
				"build",
				"chore",
				"ci",
				"docs",
				"feat",
				"fix",
				"perf",
				"refactor",
				"revert",
				"style",
				"test",
				"patch",
			],
		],
	},
};


================================================
FILE: crowdin.yml
================================================
languages_mapping:
  two_letters_code:
    pt-BR: pt_BR
files:
  - source: /hrms/locale/main.pot
    translation: /hrms/locale/%two_letters_code%.po
pull_request_title: 'fix: sync translations from crowdin'
pull_request_labels:
  - translation
  - skip-release-notes
commit_message: 'fix: %language% translations'
append_commit_message: false


================================================
FILE: docker/docker-compose.yml
================================================
version: "3.8" # Updated version
services:
  mariadb:
    image: mariadb:10.8
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --skip-character-set-client-handshake
      - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6
    environment:
      MYSQL_ROOT_PASSWORD: 123
    volumes:
      - mariadb-data:/var/lib/mysql

  redis:
    image: redis:alpine

  frappe:
    image: frappe/bench:latest
    command: bash /workspace/init.sh
    environment:
      - SHELL=/bin/bash
    working_dir: /home/frappe
    volumes:
      - .:/workspace
    ports:
      - 8000:8000
      - 9000:9000

volumes:
  mariadb-data:


================================================
FILE: docker/init.sh
================================================
#!bin/bash

if [ -d "/home/frappe/frappe-bench/apps/frappe" ]; then
    echo "Bench already exists, skipping init"
    cd frappe-bench
    bench start
else
    echo "Creating new bench..."
fi

export PATH="${NVM_DIR}/versions/node/v${NODE_VERSION_DEVELOP}/bin/:${PATH}"

bench init --skip-redis-config-generation frappe-bench

cd frappe-bench

# Use containers instead of localhost
bench set-mariadb-host mariadb
bench set-redis-cache-host redis://redis:6379
bench set-redis-queue-host redis://redis:6379
bench set-redis-socketio-host redis://redis:6379

# Remove redis, watch from Procfile
sed -i '/redis/d' ./Procfile
sed -i '/watch/d' ./Procfile

bench get-app erpnext
bench get-app hrms

bench new-site hrms.localhost \
--force \
--mariadb-root-password 123 \
--admin-password admin \
--no-mariadb-socket

bench --site hrms.localhost install-app hrms
bench --site hrms.localhost set-config developer_mode 1
bench --site hrms.localhost enable-scheduler
bench --site hrms.localhost clear-cache
bench use hrms.localhost

bench start

================================================
FILE: frontend/.eslintrc.js
================================================
module.exports = {
	root: true,
	env: {
		es2021: true,
		node: true,
	},
	extends: [
		"eslint:recommended",
		"plugin:vue/vue3-essential",
		"plugin:prettier/recommended",
	],
	parserOptions: {
		ecmaVersion: 2020,
	},
	rules: {
		"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
		"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
		"vue/no-deprecated-slot-attribute": "off",
		"vue/multi-word-component-names": "off",
	},
	plugins: ["vue", "prettier"],
}


================================================
FILE: frontend/.gitignore
================================================
node_modules
.DS_Store
dev-dist
dist
dist-ssr
*.local

================================================
FILE: frontend/.prettierrc.json
================================================
{
	"semi": false,
	"tabWidth": 2,
	"useTabs": true
}


================================================
FILE: frontend/index.html
================================================
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1.0, viewport-fit=cover maximum-scale=1.0, user-scalable=no"
		/>
		<title>Frappe HR</title>

		<meta name="apple-mobile-web-app-capable" content="yes" />
		<meta name="apple-mobile-web-app-title" content="Frappe HR" />
		<meta name="apple-mobile-web-app-status-bar-style" content="white" />
		<!-- required for setting the status bar bg as white -->
		<meta name="theme-color" content="#fff" />

		<!-- PWA -->
		<link
			rel="icon"
			type="image/png"
			sizes="196x196"
			href="/assets/hrms/manifest/favicon-196.png"
		/>
		<link
			rel="apple-touch-icon"
			href="/assets/hrms/manifest/apple-icon-180.png"
		/>

		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2048-2732.jpg"
			media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2732-2048.jpg"
			media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1668-2388.jpg"
			media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2388-1668.jpg"
			media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1536-2048.jpg"
			media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2048-1536.jpg"
			media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1668-2224.jpg"
			media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2224-1668.jpg"
			media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1620-2160.jpg"
			media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2160-1620.jpg"
			media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1290-2796.jpg"
			media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2796-1290.jpg"
			media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1179-2556.jpg"
			media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2556-1179.jpg"
			media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1284-2778.jpg"
			media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2778-1284.jpg"
			media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1170-2532.jpg"
			media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2532-1170.jpg"
			media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1125-2436.jpg"
			media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2436-1125.jpg"
			media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1242-2688.jpg"
			media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2688-1242.jpg"
			media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-828-1792.jpg"
			media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1792-828.jpg"
			media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1242-2208.jpg"
			media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-2208-1242.jpg"
			media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-750-1334.jpg"
			media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1334-750.jpg"
			media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-640-1136.jpg"
			media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
		/>
		<link
			rel="apple-touch-startup-image"
			href="/assets/hrms/manifest/apple-splash-1136-640.jpg"
			media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
		/>
	</head>
	<body class="antialiased">
		<div id="app"></div>
		<div id="modals"></div>
		<div id="popovers"></div>

		<script>
			window.csrf_token = "{{ csrf_token }}"
			window.site_name = '{{ site_name }}'
			if (!window.frappe) window.frappe = {}
			frappe.boot = {{ boot }}
		</script>

		<script type="module" src="/src/main.js"></script>
	</body>
</html>


================================================
FILE: frontend/ionic.config.json
================================================
{
	"name": "FrappeHR",
	"integrations": {},
	"type": "vue",
	"server": {
		"url": "http://localhost:8080/"
	}
}


================================================
FILE: frontend/jsconfig.json
================================================
{
	"compilerOptions": {
		"allowJs": true
	}
}


================================================
FILE: frontend/package.json
================================================
{
	"name": "frappe-hr-ui",
	"private": true,
	"version": "0.0.0",
	"type": "module",
	"scripts": {
		"dev": "vite",
		"serve": "vite preview",
		"build": "vite build --base=/assets/hrms/frontend/ && yarn copy-html-entry",
		"ionic:build": "npm run build",
		"ionic:serve": "vite dev --host",
		"copy-html-entry": "cp ../hrms/public/frontend/index.html ../hrms/www/hrms.html"
	},
	"dependencies": {
		"@ionic/vue": "^7.4.3",
		"@ionic/vue-router": "^7.4.3",
		"@vitejs/plugin-vue": "^4.4.0",
		"autoprefixer": "^10.4.19",
		"dayjs": "^1.11.11",
		"feather-icons": "^4.29.1",
		"firebase": "^10.8.0",
		"frappe-ui": "0.1.105",
		"postcss": "^8.4.5",
		"tailwindcss": "^3.4.3",
		"vite": "^5.4.10",
		"vite-plugin-pwa": "^0.20.5",
		"vue": "^3.5.12",
		"vue-router": "^4.3.2",
		"workbox-core": "^7.0.0",
		"workbox-precaching": "^7.0.0"
	},
	"devDependencies": {
		"eslint": "^8.39.0",
		"eslint-plugin-vue": "^9.11.0",
		"prettier": "^2.8.8"
	}
}


================================================
FILE: frontend/postcss.config.js
================================================
export default {
	plugins: {
		tailwindcss: {},
		autoprefixer: {},
	},
}


================================================
FILE: frontend/public/frappe-push-notification.js
================================================
import { initializeApp } from "firebase/app"
import {
	getMessaging,
	getToken,
	isSupported,
	deleteToken,
	onMessage as onFCMMessage,
} from "firebase/messaging"

class FrappePushNotification {
	static get relayServerBaseURL() {
		return window.frappe?.boot.push_relay_server_url
	}

	// Type definitions
	/**
	 * Web Config
	 * FCM web config to initialize firebase app
	 *
	 * @typedef {object} webConfigType
	 * @property {string} projectId
	 * @property {string} appId
	 * @property {string} apiKey
	 * @property {string} authDomain
	 * @property {string} messagingSenderId
	 */

	/**
	 * Constructor
	 *
	 * @param {string} projectName
	 */
	constructor(projectName) {
		// client info
		this.projectName = projectName
		/** @type {webConfigType | null}  */
		this.webConfig = null
		this.vapidPublicKey = ""
		this.token = null

		// state
		this.initialized = false
		this.messaging = null
		/** @type {ServiceWorkerRegistration | null} */
		this.serviceWorkerRegistration = null

		// event handlers
		this.onMessageHandler = null
	}

	/**
	 * Initialize notification service client
	 *
	 * @param {ServiceWorkerRegistration} serviceWorkerRegistration - Service worker registration object
	 * @returns {Promise<void>}
	 */
	async initialize(serviceWorkerRegistration) {
		if (this.initialized) {
			return
		}
		this.serviceWorkerRegistration = serviceWorkerRegistration
		const config = await this.fetchWebConfig()
		this.messaging = getMessaging(initializeApp(config))
		this.onMessage(this.onMessageHandler)
		this.initialized = true
	}

	/**
	 * Append config to service worker URL
	 *
	 * @param {string} url - Service worker URL
	 * @param {string} parameter_name - Parameter name to add config
	 * @returns {Promise<string>} - Service worker URL with config
	 */
	async appendConfigToServiceWorkerURL(url, parameter_name = "config") {
		let config = await this.fetchWebConfig()
		const encode_config = encodeURIComponent(JSON.stringify(config))
		return `${url}?${parameter_name}=${encode_config}`
	}

	/**
	 * Fetch web config of the project
	 *
	 * @returns {Promise<webConfigType>}
	 */
	async fetchWebConfig() {
		if (this.webConfig !== null && this.webConfig !== undefined) {
			return this.webConfig
		}
		try {
			let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}`
			let response = await fetch(url)
			let response_json = await response.json()
			this.webConfig = response_json.config
			return this.webConfig
		} catch (e) {
			throw new Error(
				"Push Notification Relay is not configured properly on your site."
			)
		}
	}

	/**
	 * Fetch VAPID public key
	 *
	 * @returns {Promise<string>}
	 */
	async fetchVapidPublicKey() {
		if (this.vapidPublicKey !== "") {
			return this.vapidPublicKey
		}
		try {
			let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}`
			let response = await fetch(url)
			let response_json = await response.json()
			this.vapidPublicKey = response_json.vapid_public_key
			return this.vapidPublicKey
		} catch (e) {
			throw new Error(
				"Push Notification Relay is not configured properly on your site."
			)
		}
	}

	/**
	 * Register on message handler
	 *
	 * @param {function(
	 *  {
	 *    data:{
	 *       title: string,
	 *       body: string,
	 *       click_action: string|null,
	 *    }
	 *  }
	 * )} callback - Callback function to handle message
	 */
	onMessage(callback) {
		if (callback == null) return
		this.onMessageHandler = callback
		if (this.messaging == null) return
		onFCMMessage(this.messaging, this.onMessageHandler)
	}

	/**
	 * Check if notification is enabled
	 *
	 * @returns {boolean}
	 */
	isNotificationEnabled() {
		return localStorage.getItem(`firebase_token_${this.projectName}`) !== null
	}

	/**
	 * Enable notification
	 * This will return notification permission status and token
	 *
	 * @returns {Promise<{permission_granted: boolean, token: string}>}
	 */
	async enableNotification() {
		if (!(await isSupported())) {
			throw new Error("Push notifications are not supported on your device")
		}
		// Return if token already presence in the instance
		if (this.token != null) {
			return {
				permission_granted: true,
				token: this.token,
			}
		}
		// ask for permission
		const permission = await Notification.requestPermission()
		if (permission !== "granted") {
			return {
				permission_granted: false,
				token: "",
			}
		}
		// check in local storage for old token
		let oldToken = localStorage.getItem(`firebase_token_${this.projectName}`)
		const vapidKey = await this.fetchVapidPublicKey()
		let newToken = await getToken(this.messaging, {
			vapidKey: vapidKey,
			serviceWorkerRegistration: this.serviceWorkerRegistration,
		})
		// register new token if token is changed
		if (oldToken !== newToken) {
			// unsubscribe old token
			if (oldToken) {
				await this.unregisterTokenHandler(oldToken)
			}
			// subscribe push notification and register token
			let isSubscriptionSuccessful = await this.registerTokenHandler(newToken)
			if (isSubscriptionSuccessful === false) {
				throw new Error("Failed to subscribe to push notification")
			}
			// save token to local storage
			localStorage.setItem(`firebase_token_${this.projectName}`, newToken)
		}
		this.token = newToken
		return {
			permission_granted: true,
			token: newToken,
		}
	}

	/**
	 * Disable notification
	 * This will delete token from firebase and unsubscribe from push notification
	 *
	 * @returns {Promise<void>}
	 */
	async disableNotification() {
		if (this.token == null) {
			// try to fetch token from local storage
			this.token = localStorage.getItem(`firebase_token_${this.projectName}`)
			if (this.token == null || this.token === "") {
				return
			}
		}
		// delete old token from firebase
		try {
			await deleteToken(this.messaging)
		} catch (e) {
			console.error("Failed to delete token from firebase")
			console.error(e)
		}
		try {
			await this.unregisterTokenHandler(this.token)
		} catch {
			console.error("Failed to unsubscribe from push notification")
			console.error(e)
		}
		// remove token
		localStorage.removeItem(`firebase_token_${this.projectName}`)
		this.token = null
	}

	/**
	 * Register Token Handler
	 *
	 * @param {string} token - FCM token returned by {@link enableNotification} method
	 * @returns {promise<boolean>}
	 */
	async registerTokenHandler(token) {
		try {
			let response = await fetch(
				"/api/method/frappe.push_notification.subscribe?fcm_token=" +
					token +
					"&project_name=" +
					this.projectName,
				{
					method: "GET",
					headers: {
						"Content-Type": "application/json",
					},
				}
			)
			return response.status === 200
		} catch (e) {
			console.error(e)
			return false
		}
	}

	/**
	 * Unregister Token Handler
	 *
	 * @param {string} token - FCM token returned by `enableNotification` method
	 * @returns {promise<boolean>}
	 */
	async unregisterTokenHandler(token) {
		try {
			let response = await fetch(
				"/api/method/frappe.push_notification.unsubscribe?fcm_token=" +
					token +
					"&project_name=" +
					this.projectName,
				{
					method: "GET",
					headers: {
						"Content-Type": "application/json",
					},
				}
			)
			return response.status === 200
		} catch (e) {
			console.error(e)
			return false
		}
	}
}

export default FrappePushNotification


================================================
FILE: frontend/public/sw.js
================================================
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching"
import { clientsClaim } from "workbox-core"

import { initializeApp } from "firebase/app"
import { getMessaging, onBackgroundMessage } from "firebase/messaging/sw"

// Use the precache manifest generated by Vite
precacheAndRoute(self.__WB_MANIFEST)

// Clean up old caches
cleanupOutdatedCaches()

const jsonConfig = new URL(location).searchParams.get("config")

// Firebase config initialization
try {
	const firebaseApp = initializeApp(JSON.parse(jsonConfig))
	const messaging = getMessaging(firebaseApp)

	function isChrome() {
		return navigator.userAgent.toLowerCase().includes("chrome")
	}

	onBackgroundMessage(messaging, (payload) => {
		const notificationTitle = payload.data.title
		let notificationOptions = {
			body: payload.data.body || "",
		}
		if (payload.data.notification_icon) {
			notificationOptions["icon"] = payload.data.notification_icon
		}
		if (isChrome()) {
			notificationOptions["data"] = {
				url: payload.data.click_action,
			}
		} else {
			if (payload.data.click_action) {
				notificationOptions["actions"] = [
					{
						action: payload.data.click_action,
						title: "View Details",
					},
				]
			}
		}
		self.registration.showNotification(notificationTitle, notificationOptions)
	})

	if (isChrome()) {
		self.addEventListener("notificationclick", (event) => {
			event.stopImmediatePropagation()
			event.notification.close()
			if (event.notification.data && event.notification.data.url) {
				clients.openWindow(event.notification.data.url)
			}
		})
	}
} catch (error) {
	console.log("Failed to initialize Firebase", error)
}

self.skipWaiting()
clientsClaim()
console.log("Service Worker Initialized")


================================================
FILE: frontend/src/App.vue
================================================
<template>
	<ion-app>
		<ion-router-outlet id="main-content" />
		<Toasts />

		<InstallPrompt />
	</ion-app>
</template>

<script setup>
import { onMounted } from "vue"
import { IonApp, IonRouterOutlet } from "@ionic/vue"

import { Toasts } from "frappe-ui"

import InstallPrompt from "@/components/InstallPrompt.vue"
import { showNotification } from "@/utils/pushNotifications"

onMounted(() => {
	window?.frappePushNotification?.onMessage((payload) => {
		showNotification(payload)
	})
})
</script>


================================================
FILE: frontend/src/components/AttendanceCalendar.vue
================================================
<template>
	<div class="flex flex-col w-full gap-5" v-if="calendarEvents.data">
		<div class="text-lg text-gray-800 font-bold">{{ __("Attendance Calendar") }}</div>

		<div class="flex flex-col gap-6 bg-white py-6 px-3.5 rounded-lg border-none">
			<!-- Month Change -->
			<div class="flex flex-row justify-between items-center px-4">
				<Button
					icon="chevron-left"
					variant="ghost"
					@click="firstOfMonth = firstOfMonth.subtract(1, 'M')"
				/>
				<span class="text-lg text-gray-800 font-bold">
					{{ firstOfMonth.format("MMMM") }} {{ firstOfMonth.format("YYYY") }}
				</span>
				<Button
					icon="chevron-right"
					variant="ghost"
					@click="firstOfMonth = firstOfMonth.add(1, 'M')"
				/>
			</div>

			<!-- Calendar -->
			<div class="grid grid-cols-7 gap-y-3">
				<div
					v-for="day in DAYS"
					class="flex justify-center text-gray-600 text-sm font-medium leading-6"
				>
					{{ day }}
				</div>
				<div v-for="_ in firstOfMonth.get('d')" />
				<div v-for="index in firstOfMonth.endOf('M').get('D')">
					<div
						class="h-8 w-8 flex rounded-full mx-auto"
						:class="getEventOnDate(index) && colorMap[getEventOnDate(index)]"
					>
						<span class="text-gray-800 text-sm font-medium m-auto">
							{{ index }}
						</span>
					</div>
				</div>
			</div>

			<hr />

			<!-- Summary -->
			<div class="grid grid-cols-4 mx-2">
				<div v-for="status in summaryStatuses" class="flex flex-col gap-1">
					<div class="flex flex-row gap-1 items-center">
						<span class="rounded full h-3 w-3" :class="colorMap[status]" />
						<span class="text-gray-600 text-sm font-medium leading-5"> {{ __(status) }} </span>
					</div>
					<span class="text-gray-800 text-base font-semibold leading-6 mx-auto">
						{{ summary[status] || 0 }}
					</span>
				</div>
			</div>
		</div>
	</div>
</template>

<script setup>
import { computed, inject, ref, watch } from "vue"
import { createResource } from "frappe-ui"

const dayjs = inject("$dayjs")
const __ = inject("$translate")
const firstOfMonth = ref(dayjs().date(1).startOf("D"))

const colorMap = {
	Present: "bg-green-300",
	"Work From Home": "bg-green-300",
	"Half Day": "bg-yellow-200",
	Absent: "bg-red-200",
	"On Leave": "bg-blue-300",
	Holiday: "bg-gray-300",
}

// __("Present"), __("Half Day"), __("Absent"), __("On Leave"), __("Work From Home")
const summaryStatuses = ["Present", "Half Day", "Absent", "On Leave"]

const summary = computed(() => {
	const summary = {}

	for (const status of Object.values(calendarEvents.data)) {
		let updatedStatus = status === "Work From Home" ? "Present" : status
		if (updatedStatus in summary) {
			summary[updatedStatus] += 1
		} else {
			summary[updatedStatus] = 1
		}
	}

	return summary
})

watch(
	() => firstOfMonth.value,
	() => {
		calendarEvents.fetch()
	}
)

const getEventOnDate = (date) => {
	return calendarEvents.data[firstOfMonth.value.date(date).format("YYYY-MM-DD")]
}

const getFirstLetter = (s) => Array.from(s.trim())[0] // Unicode

const DAYS = [
	getFirstLetter(__("Sunday")),
	getFirstLetter(__("Monday")),
	getFirstLetter(__("Tuesday")),
	getFirstLetter(__("Wednesday")),
	getFirstLetter(__("Thursday")),
	getFirstLetter(__("Friday")),
	getFirstLetter(__("Saturday")),
]

//resources
const calendarEvents = createResource({
	url: "hrms.api.get_attendance_calendar_events",
	auto: true,
	cache: "hrms:attendance_calendar_events",
	makeParams() {
		return {
			from_date: firstOfMonth.value.format("YYYY-MM-DD"),
			to_date: firstOfMonth.value.endOf("M").format("YYYY-MM-DD"),
		}
	},
})
</script>


================================================
FILE: frontend/src/components/AttendanceRequestItem.vue
================================================
<template>
	<ListItem
		:isTeamRequest="props.isTeamRequest"
		:employee="props.doc.employee"
		:employeeName="props.doc.employee_name"
		>
		<template #left>
			<AttendanceIcon class="h-5 w-5 text-gray-500" />
			<div class="flex flex-col items-start gap-1.5">
				<div class="text-base font-normal text-gray-800">
					{{ props.doc.reason }}
				</div>
				<div class="text-xs font-normal text-gray-500">
					<span>{{ props.doc.attendance_dates || getDates(props.doc) }}</span>
					<span v-if="props.doc.to_date">
						<span class="whitespace-pre"> &middot; </span>
						<span class="whitespace-nowrap">{{ __("{0}d", [props.doc.total_attendance_days]) }}</span>
					</span>
				</div>
			</div>
		</template>
		<template #right>
			<Badge variant="outline" :theme="colorMap[status]" :label="__(status)" size="md" />
			<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
		</template>
	</ListItem>
</template>

<script setup>
import { computed } from "vue"
import { Badge, FeatherIcon } from "frappe-ui"

import ListItem from "@/components/ListItem.vue"
import AttendanceIcon from "@/components/icons/AttendanceIcon.vue"
import { getDates, getTotalDays } from "@/data/attendance"

const props = defineProps({
	doc: {
		type: Object,
	},
	workflowStateField: {
		type: String,
		required: false,
	},
})

const status = computed(() => {
	if (props.workflowStateField) return props.doc[props.workflowStateField]
	return props.doc.docstatus ? "Submitted" : "Draft"
})

const colorMap = {
	Draft: "gray",
	Submitted: "blue",
}
</script>


================================================
FILE: frontend/src/components/BaseLayout.vue
================================================
<template>
	<ion-page>
		<ion-header class="ion-no-border">
			<div class="w-full sm:w-96">
				<div class="flex flex-col bg-white shadow-sm p-4">
					<div class="flex flex-row justify-between items-center">
						<div class="flex flex-row items-center gap-2">
							<h2 class="text-xl font-bold text-gray-900">
								{{ props.pageTitle || __("Frappe HR") }}
							</h2>
						</div>
						<div class="flex flex-row items-center gap-3 ml-auto">
							<router-link
								:to="{ name: 'Notifications' }"
								v-slot="{ navigate }"
								class="flex flex-col items-center"
							>
								<span class="relative inline-block" @click="navigate">
									<FeatherIcon name="bell" class="h-6 w-6" />
									<span
										v-if="unreadNotificationsCount.data"
										class="absolute top-0 right-0.5 inline-block w-2 h-2 bg-red-600 rounded-full border border-white"
									>
									</span>
								</span>
							</router-link>
							<router-link
								:to="{ name: 'Profile' }"
								class="flex flex-col items-center"
							>
								<Avatar
									:image="user.data.user_image"
									:label="user.data.first_name"
									size="xl"
								/>
							</router-link>
						</div>
					</div>
				</div>
			</div>
		</ion-header>

		<ion-content class="ion-no-padding">
			<div class="flex flex-col h-screen w-screen sm:w-96">
				<slot name="body"></slot>
			</div>
		</ion-content>
	</ion-page>
</template>

<script setup>
import { IonHeader, IonContent, IonPage } from "@ionic/vue"
import { FeatherIcon, Avatar } from "frappe-ui"

import { unreadNotificationsCount } from "@/data/notifications"

import { inject } from "vue"

const user = inject("$user")

const props = defineProps({
	pageTitle: {
		type: String,
		required: false,
		default: "",
	},
})
</script>


================================================
FILE: frontend/src/components/BottomTabs.vue
================================================
<template>
	<ion-tab-bar
		slot="bottom"
		class="bg-white shadow-md sm:w-96 py-2 pb-2 standalone:pb-safe-bottom"
	>
		<ion-tab-button
			v-for="item in tabItems"
			:key="item.title"
			:tab="item.title"
			:href="item.route"
			:class="[
				'bg-white text-xs space-y-1.5 !hover:border-gray-300 !hover:text-gray-700 transition active:scale-95',
				route.path === item.route
					? 'border-gray-900 text-gray-800 font-semibold'
					: 'text-gray-600 font-normal',
			]"
		>
			<component :is="item.icon" class="h-5 w-5" />
			<div>{{ item.title }}</div>
		</ion-tab-button>
	</ion-tab-bar>
</template>

<script setup>
import { useRoute } from "vue-router"

import { IonTabBar, IonTabButton, IonLabel } from "@ionic/vue"

import HomeIcon from "@/components/icons/HomeIcon.vue"
import LeaveIcon from "@/components/icons/LeaveIcon.vue"
import ExpenseIcon from "@/components/icons/ExpenseIcon.vue"
import SalaryIcon from "@/components/icons/SalaryIcon.vue"
import AttendanceIcon from "@/components/icons/AttendanceIcon.vue"
import { inject } from "vue"

const __ = inject("$translate")

const route = useRoute()

const tabItems = [
	{
		icon: HomeIcon,
		title: __("Home"),
		route: "/home",
	},
	{
		icon: AttendanceIcon,
		title: __("Attendance"),
		route: "/dashboard/attendance",
	},
	{
		icon: LeaveIcon,
		title: __("Leaves"),
		route: "/dashboard/leaves",
	},
	{
		icon: ExpenseIcon,
		title: __("Expenses"),
		route: "/dashboard/expense-claims",
	},
	{
		icon: SalaryIcon,
		title: __("Salary"),
		route: "/dashboard/salary-slips",
	},
]
</script>


================================================
FILE: frontend/src/components/CheckInPanel.vue
================================================
<template>
	<div class="flex flex-col bg-white rounded w-full py-6 px-4 border-none">
		<h2 class="text-lg font-bold text-gray-900">
			{{ __("Hey, {0} 👋", [employee?.data?.first_name]) }}
		</h2>

		<template v-if="settings.data?.allow_employee_checkin_from_mobile_app">
			<div class="font-medium text-sm text-gray-500 mt-1.5" v-if="lastLog">
				<span>{{ __("Last {0} was at {1}", [__(lastLogType), formatTimestamp(lastLog.time)]) }}</span>
				<span class="whitespace-pre"> &middot; </span>
				<router-link :to="{ name: 'EmployeeCheckinListView' }" v-slot="{ navigate }">
					<span @click="navigate" class="underline">View List</span>
				</router-link>
			</div>
			<Button
				class="mt-4 mb-1 drop-shadow-sm py-5 text-base"
				id="open-checkin-modal"
				@click="handleEmployeeCheckin"
			>
				<template #prefix>
					<FeatherIcon
						:name="nextAction.action === 'IN' ? 'arrow-right-circle' : 'arrow-left-circle'"
						class="w-4"
					/>
				</template>
				{{ nextAction.label }}
			</Button>
		</template>

		<div v-else class="font-medium text-sm text-gray-500 mt-1.5">
			{{ dayjs().format("ddd, D MMMM, YYYY") }}
		</div>
	</div>

	<ion-modal
		v-if="settings.data?.allow_employee_checkin_from_mobile_app"
		ref="modal"
		trigger="open-checkin-modal"
		:initial-breakpoint="1"
		:breakpoints="[0, 1]"
	>
		<div class="h-120 w-full flex flex-col items-center justify-center gap-5 p-4 mb-5">
			<div class="flex flex-col gap-1.5 mt-2 items-center justify-center">
				<div class="font-bold text-xl">
					{{ dayjs(checkinTimestamp).format("hh:mm:ss a") }}
				</div>
				<div class="font-medium text-gray-500 text-sm">
					{{ dayjs().format("D MMM, YYYY") }}
				</div>
			</div>

			<template v-if="settings.data?.allow_geolocation_tracking">
				<span v-if="locationStatus" class="font-medium text-gray-500 text-sm">
					{{ locationStatus }}
				</span>

				<div class="rounded border-4 translate-z-0 block overflow-hidden w-full h-170">
					<iframe
						width="100%"
						height="170"
						frameborder="0"
						scrolling="no"
						marginheight="0"
						marginwidth="0"
						style="border: 0"
						:src="`https://maps.google.com/maps?q=${latitude},${longitude}&hl=en&z=15&amp;output=embed`"
					>
					</iframe>
				</div>
			</template>

			<Button :loading="checkins.insert.loading" variant="solid" class="w-full py-5 text-sm disabled:bg-gray-700" @click="submitLog(nextAction.action)">
				{{ __("Confirm {0}", [nextAction.label]) }}
			</Button>
		</div>
	</ion-modal>
</template>

<script setup>
import { createResource, createListResource, toast, FeatherIcon } from "frappe-ui"
import { computed, inject, ref, onMounted, onBeforeUnmount } from "vue"
import { IonModal, modalController } from "@ionic/vue"

import { formatTimestamp } from "@/utils/formatters"

const DOCTYPE = "Employee Checkin"

const socket = inject("$socket")
const employee = inject("$employee")
const dayjs = inject("$dayjs")
const __ = inject("$translate")
const checkinTimestamp = ref(null)
const latitude = ref(0)
const longitude = ref(0)
const locationStatus = ref("")
const settings = createResource({
	url: "hrms.api.get_hr_settings",
	auto: true,
})

const checkins = createListResource({
	doctype: DOCTYPE,
	fields: ["name", "employee", "employee_name", "log_type", "time", "device_id"],
	filters: {
		employee: employee.data.name,
	},
	orderBy: "time desc",
})
checkins.reload()

const lastLog = computed(() => {
	if (checkins.list.loading || !checkins.data) return {}
	return checkins.data[0]
})

const lastLogType = computed(() => {
	return lastLog?.value?.log_type === "IN" ? "check-in" : "check-out"
})

const nextAction = computed(() => {
	return lastLog?.value?.log_type === "IN"
		? { action: "OUT", label: __("Check Out") }
		: { action: "IN", label: __("Check In") }
})

function handleLocationSuccess(position) {
	latitude.value = position.coords.latitude
	longitude.value = position.coords.longitude

	locationStatus.value = [
		__("Latitude: {0}°", [Number(latitude.value).toFixed(5)]),
		__("Longitude: {0}°", [Number(longitude.value).toFixed(5)]),
	].join(", ")
}

function handleLocationError(error) {
	locationStatus.value = "Unable to retrieve your location"
	if (error) locationStatus.value += `: ERROR(${error.code}): ${error.message}`
}

const fetchLocation = () => {
	if (!navigator.geolocation) {
		locationStatus.value = __("Geolocation is not supported by your current browser")
	} else {
		locationStatus.value = __("Locating...")
		navigator.geolocation.getCurrentPosition(handleLocationSuccess, handleLocationError)
	}
}

const handleEmployeeCheckin = () => {
	checkinTimestamp.value = dayjs().format("YYYY-MM-DD HH:mm:ss")

	if (settings.data?.allow_geolocation_tracking) {
		fetchLocation()
	}
}

const submitLog = (logType) => {
	const actionLabel = logType === "IN" ? __("Check-in") : __("Check-out")

	checkins.insert.submit(
		{
			employee: employee.data.name,
			log_type: logType,
			time: checkinTimestamp.value,
			latitude: latitude.value,
			longitude: longitude.value,
		},
		{
			onSuccess() {
				modalController.dismiss()
				toast({
					title: __("Success"),
					text: __("{0} successful!", [actionLabel]),
					icon: "check-circle",
					position: "bottom-center",
					iconClasses: "text-green-500",
				})
			},
			onError(error) {
				let messages = error.messages || []

				for (const message of messages) {
					toast({
						title: __("Error"),
						text: message || __("{0} failed!", [actionLabel]),
						icon: "alert-circle",
						position: "bottom-center",
						iconClasses: "text-red-500",
					})
				}
			},
		}
	)
}

onMounted(() => {
	socket.emit("doctype_subscribe", DOCTYPE)
	socket.on("list_update", (data) => {
		if (data.doctype == DOCTYPE) {
			checkins.reload()
		}
	})
})

onBeforeUnmount(() => {
	socket.emit("doctype_unsubscribe", DOCTYPE)
	socket.off("list_update")
})
</script>


================================================
FILE: frontend/src/components/CustomIonModal.vue
================================================
<template>
	<ion-modal
		ref="modal"
		:trigger="trigger"
		:initial-breakpoint="1"
		:breakpoints="[0, 1]"
		:backdrop-breakpoint="1"
		:is-open="isOpen"
		@willPresent="showModalBackdrop = true"
		@willDismiss="showModalBackdrop = false"
		@didDismiss="() => emit('did-dismiss')"
	>
		<slot name="actionSheet"></slot>
	</ion-modal>

	<!-- backdrop -->
	<div
		v-if="showModalBackdrop"
		class="fixed inset-0 z-[10000] !mt-0 bg-black opacity-30 cursor-pointer"
		@click="() => modalController.dismiss()"
	></div>
</template>

<script setup>
/**
 * Problem: ion-modal traps focus inside the modal making controls like autocomplete unusable inside it
 * @see https://github.com/ionic-team/ionic-framework/issues/24646
 * This custom ion-modal disables backdrop using backdrop-breakpoint=1 and we add a custom backdrop
 */
import { ref } from "vue"
import { IonModal, modalController } from "@ionic/vue"

const props = defineProps({
	trigger: {
		type: String,
		required: false,
	},
	isOpen: {
		type: Boolean,
		required: false,
	},
})
const emit = defineEmits(["did-dismiss"])
const showModalBackdrop = ref(false)
</script>


================================================
FILE: frontend/src/components/EmployeeAdvanceBalance.vue
================================================
<template>
	<div
		class="flex flex-col bg-white rounded mt-5 overflow-auto"
		v-if="props.items?.length"
	>
		<router-link
			v-for="link in props.items"
			:key="link.name"
			:to="{ name: 'EmployeeAdvanceDetailView', params: { id: link.name } }"
			class="flex flex-row p-3.5 items-center justify-between border-b cursor-pointer"
		>
			<EmployeeAdvanceItem :doc="link" />
		</router-link>

		<router-link
			:to="{ name: 'EmployeeAdvanceFormView' }"
			v-slot="{ navigate }"
		>
			<div class="flex flex-col bg-white w-full py-5 px-3.5 mt-0 border-none">
				<Button @click="navigate" variant="subtle" class="py-5 text-base">
					{{ __("Request an Advance") }}
				</Button>
			</div>
		</router-link>
	</div>
	<EmptyState :message="__('You have no advances')" v-else />
</template>

<script setup>
import EmployeeAdvanceItem from "@/components/EmployeeAdvanceItem.vue"
import { inject } from "vue"

const __ = inject("$translate")
const props = defineProps({
	items: {
		type: Array,
	},
})
</script>


================================================
FILE: frontend/src/components/EmployeeAdvanceItem.vue
================================================
<template>
	<ListItem
		:isTeamRequest="props.isTeamRequest"
		:employee="props.doc.employee"
		:employeeName="props.doc.employee_name"
	>
		<template #left>
			<EmployeeAdvanceIcon class="h-5 w-5 mt-[3px] text-gray-500" />
			<div class="flex flex-col items-start gap-1">
				<div v-if="props.doc.balance_amount" class="text-lg font-bold text-gray-800 leading-6">
					{{ formatCurrency(props.doc.balance_amount, props.doc.currency) }}
					/
					<span class="text-gray-600">
						{{ formatCurrency(props.doc.paid_amount, props.doc.currency) }}
					</span>
				</div>
				<div v-else class="text-lg font-bold text-gray-800 leading-6">
					{{ formatCurrency(props.doc.advance_amount, props.doc.currency) }}
				</div>
				<div class="text-xs font-normal text-gray-500">
					<span>
						{{ __(props.doc.purpose) }}
					</span>
					<span class="whitespace-pre"> &middot; </span>
					<span class="whitespace-nowrap">
						{{ postingDate }}
					</span>
				</div>
			</div>
		</template>
		<template #right>
			<Badge variant="outline" :theme="colorMap[status]" :label="__(status, null, 'Employee Advance')" size="md" />
			<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
		</template>
	</ListItem>
</template>

<script setup>
import { FeatherIcon, Badge } from "frappe-ui"
import { computed, inject } from "vue"

import ListItem from "@/components/ListItem.vue"
import EmployeeAdvanceIcon from "@/components/icons/EmployeeAdvanceIcon.vue"
import { formatCurrency } from "@/utils/formatters"

const dayjs = inject("$dayjs")
const props = defineProps({
	doc: {
		type: Object,
	},
	isTeamRequest: {
		type: Boolean,
		default: false,
	},
	workflowStateField: {
		type: String,
		required: false,
	},
})

const colorMap = {
	Paid: "green",
	Unpaid: "orange",
	Claimed: "blue",
	Returned: "gray",
	"Partly Claimed and Returned": "orange",
}

const postingDate = computed(() => {
	return dayjs(props.doc.posting_date).format("D MMM")
})

const status = computed(() => {
	return props.workflowStateField ? props.doc[props.workflowStateField] : props.doc.status
})
</script>


================================================
FILE: frontend/src/components/EmployeeAvatar.vue
================================================
<template>
	<div v-if="showLabel" class="flex flex-row items-center gap-2">
		<Avatar
			v-if="employee"
			:label="employee?.employee_name"
			:image="employee?.image"
			:size="props.size"
		/>
		<div class="text-base text-gray-800 grow">
			{{ employee?.employee_name }}
		</div>
	</div>

	<Avatar
		v-else
		:label="employee?.employee_name"
		:image="employee?.image"
		:size="props.size"
	/>
</template>

<script setup>
import { computed } from "vue"
import { Avatar } from "frappe-ui"
import { getEmployeeInfo, getEmployeeInfoByUserID } from "@/data/employees"

const props = defineProps({
	employeeID: {
		type: String,
		required: false,
	},
	userID: {
		type: String,
		required: false,
	},
	size: {
		type: String,
		default: "sm",
	},
	showLabel: {
		type: Boolean,
		default: false,
	},
})

const employee = computed(() => {
	if (props.employeeID) {
		return getEmployeeInfo(props.employeeID)
	} else if (props.userID) {
		return getEmployeeInfoByUserID(props.userID)
	}
})
</script>


================================================
FILE: frontend/src/components/EmployeeCheckinItem.vue
================================================
<template>
	<ListItem>
		<template #left>
			<FeatherIcon name="clock" class="h-5 w-5 text-gray-500" />
			<div class="flex flex-col items-start gap-1.5">
				<div class="text-base font-normal text-gray-800">Log Type: {{ props.doc.log_type }}</div>
				<div class="text-xs font-normal text-gray-500">
					<span>{{ formatTimestamp(props.doc.time) }}</span>
				</div>
			</div>
		</template>
		<template #right>
			<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
		</template>
	</ListItem>
</template>

<script setup>
import { FeatherIcon } from "frappe-ui"

import ListItem from "@/components/ListItem.vue"
import { formatTimestamp } from "@/utils/formatters"

const props = defineProps({
	doc: {
		type: Object,
	},
})
</script>


================================================
FILE: frontend/src/components/EmptyState.vue
================================================
<template>
	<div
		class="flex flex-col items-center rounded p-5 text-sm text-gray-600"
		:class="[
			props.isTableField ? 'border-2 border-dashed border-gray-300 mt-5' : '',
		]"
	>
		{{ __(props.message) }}
	</div>
</template>

<script setup>
const props = defineProps({
	message: { type: String },
	isTableField: { type: Boolean, default: false },
})
</script>


================================================
FILE: frontend/src/components/ExpenseAdvancesTable.vue
================================================
<template>
	<div class="flex flex-row justify-between items-center">
		<h2 class="text-base font-semibold text-gray-800">
			{{ __("Settle against Advances") }}
		</h2>
	</div>

	<div class="flex flex-col gap-2.5" v-if="expenseClaim.advances?.length">
		<!-- Advance Card -->
		<div
			v-for="advance in expenseClaim.advances"
			:key="advance.name"
			class="flex flex-col bg-white border shadow-sm rounded p-3.5"
			:class="[
				advance.selected ? 'border-gray-500' : '',
				isReadOnly ? '' : 'cursor-pointer',
			]"
			@click="toggleAdvanceSelection(advance)"
		>
			<div class="flex flex-row justify-between items-center">
				<div class="flex flex-row items-start gap-3">
					<FormControl
						type="checkbox"
						class="mt-[0.5px]"
						v-model="advance.selected"
						:disabled="isReadOnly"
					/>

					<div class="flex flex-col items-start gap-1.5">
						<div class="text-base font-semibold text-gray-800">
							{{ advance.purpose || advance.employee_advance }}
						</div>
						<div class="flex flex-row items-center gap-3 justify-between">
							<div class="text-xs font-normal text-gray-500">
								{{ __("{0}: {1}", [
									__("Unclaimed Amount"),
									formatCurrency(advance.unclaimed_amount, currency),
								]) }}
							</div>
						</div>
					</div>
				</div>

				<div class="flex flex-row items-center gap-2">
					<span class="text-normal">
						{{ currencySymbol }}
					</span>
					<Input
						type="number"
						class="w-20"
						v-model="advance.allocated_amount"
						@input="(v) => (advance.selected = v)"
						@click.stop
						:disabled="isReadOnly"
						:max="advance.unclaimed_amount"
						min="0"
					/>
				</div>
			</div>
		</div>
	</div>

	<EmptyState v-else :message="__('No advances found')" :isTableField="true" />
</template>

<script setup>
import { computed, inject } from "vue"
import { getCurrencySymbol } from "@/data/currencies"
import { formatCurrency } from "@/utils/formatters"

const __ = inject("$translate")
const props = defineProps({
	expenseClaim: {
		type: Object,
		required: true,
	},
	currency: {
		type: String,
		required: true,
	},
	isReadOnly: {
		type: Boolean,
		default: false,
	},
})

const currencySymbol = computed(() => getCurrencySymbol(props.currency))

function toggleAdvanceSelection(advance) {
	if (props.isReadOnly) return
	advance.selected = !advance.selected
}
</script>


================================================
FILE: frontend/src/components/ExpenseClaimItem.vue
================================================
<template>
	<ListItem
		:isTeamRequest="props.isTeamRequest"
		:employee="props.doc.employee"
		:employeeName="props.doc.employee_name"
	>
		<template #left>
			<ExpenseIcon class="h-5 w-5 text-gray-500" />
			<div class="flex flex-col items-start gap-1.5">
				<div class="text-base font-normal text-gray-800">
					{{ claimTitle }}
				</div>
				<div class="text-xs font-normal text-gray-500">
					<span>{{ claimDates }}</span>
					<span class="whitespace-pre"> &middot; </span>
					<span class="whitespace-nowrap">
						{{ formatCurrency(props.doc.total_claimed_amount, currency) }}
					</span>
				</div>
			</div>
		</template>
		<template #right>
			<Badge variant="outline" :theme="statusMap[status]" :label="__(status, null, 'Expense Claim')" size="md" />
			<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
		</template>
	</ListItem>
</template>

<script setup>
import { FeatherIcon, Badge } from "frappe-ui"
import { computed, inject } from "vue"

import ListItem from "@/components/ListItem.vue"
import ExpenseIcon from "@/components/icons/ExpenseIcon.vue"

import { getCompanyCurrency } from "@/data/currencies"
import { formatCurrency } from "@/utils/formatters"

const dayjs = inject("$dayjs")
const __ = inject("$translate")
const props = defineProps({
	doc: {
		type: Object,
	},
	isTeamRequest: {
		type: Boolean,
		default: false,
	},
	workflowStateField: {
		type: String,
		required: false,
	},
})

const statusMap = {
	Draft: "gray",
	Submitted: "blue",
	Cancelled: "red",
	Paid: "green",
	Unpaid: "orange",
	"Approved & Draft": "gray",
	"Approved & Unpaid": "orange",
	"Approved & Submitted": "blue",
	Rejected: "red",
}

const status = computed(() => {
	if (props.workflowStateField) {
		return props.doc[props.workflowStateField]
	} else if (
		props.doc.approval_status === "Approved" &&
		["Draft", "Unpaid", "Submitted"].includes(props.doc.status)
	) {
		return `${props.doc.approval_status} & ${props.doc.status}`
	} else if (props.doc.approval_status === "Rejected") {
		return "Rejected"
	}
	return props.doc.status
})

const claimTitle = computed(() => {
	let title = __(props.doc.expense_type)
	if (props.doc.total_expenses > 1) {
		title = __("{0} & {1} more", [title, props.doc.total_expenses - 1])
	}
	return title
})

const claimDates = computed(() => {
	if (!props.doc.from_date && !props.doc.to_date)
		return dayjs(props.doc.posting_date).format("D MMM")

	if (props.doc.from_date === props.doc.to_date) {
		return dayjs(props.doc.from_date).format("D MMM")
	} else {
		return `${dayjs(props.doc.from_date).format("D MMM")} - ${dayjs(props.doc.to_date).format(
			"D MMM"
		)}`
	}
})

const currency = computed(() => getCompanyCurrency(props.doc.company))

const approvalStatus = computed(() => {
	return props.doc.approval_status === "Draft" ? "Pending" : props.doc.approval_status
})
</script>


================================================
FILE: frontend/src/components/ExpenseClaimSummary.vue
================================================
<template>
	<div class="flex flex-col w-full gap-5" v-if="summary.data">
		<div class="text-lg text-gray-800 font-bold">{{ __("Expense Claim Summary") }}</div>
		<div
			class="flex flex-col gap-4 bg-white py-3 px-3.5 rounded-lg border-none"
		>
			<div class="flex flex-col gap-1.5">
				<span class="text-gray-600 text-base font-medium leading-5">
					{{ __("Total Claimed Amount") }}
				</span>
				<span class="text-gray-800 text-lg font-bold leading-6">
					{{ formatCurrency(total_claimed_amount, company_currency) }}
				</span>
			</div>

			<div class="flex flex-row justify-between">
				<div class="flex flex-col gap-1">
					<div class="flex flex-row gap-1 items-center">
						<span class="text-gray-600 text-sm font-medium leading-5">
							{{ __("Pending") }}
						</span>
						<FeatherIcon name="alert-circle" class="text-yellow-500 h-3 w-3" />
					</div>
					<span class="text-gray-800 text-base font-semibold leading-6">
						{{
							formatCurrency(
								summary.data?.total_pending_amount,
								company_currency
							)
						}}
					</span>
				</div>
				<div class="flex flex-col gap-1">
					<div class="flex flex-row gap-1 items-center">
						<span class="text-gray-600 text-sm font-medium leading-5">
							{{ __("Approved") }}
						</span>
						<FeatherIcon name="check-circle" class="text-green-500 h-3 w-3" />
					</div>
					<span class="text-gray-800 text-base font-semibold leading-6">
						{{
							formatCurrency(
								summary.data?.total_approved_amount,
								company_currency
							)
						}}
					</span>
				</div>

				<div class="flex flex-col gap-1">
					<div class="flex flex-row gap-1 items-center">
						<span class="text-gray-600 text-sm font-medium leading-5">
							{{ __("Rejected") }}
						</span>
						<FeatherIcon name="x-circle" class="text-red-500 h-3 w-3" />
					</div>
					<span class="text-gray-800 text-base font-semibold leading-6">
						{{
							formatCurrency(
								summary.data?.total_rejected_amount + 
								(summary.data?.total_claimed_in_approved - summary.data?.total_approved_amount),
								company_currency
							)
						}}
					</span>
				</div>
			</div>
		</div>
	</div>
</template>

<script setup>
import { FeatherIcon } from "frappe-ui"
import { computed } from "vue"

import { expenseClaimSummary as summary } from "@/data/claims"

import { formatCurrency } from "@/utils/formatters"

const total_claimed_amount = computed(() => {
	return (
		summary.data?.total_pending_amount +
		summary.data?.total_claimed_in_approved +
		summary.data?.total_rejected_amount
	)
})

const company_currency = computed(() => summary.data?.currency)
</script>


================================================
FILE: frontend/src/components/ExpenseItems.vue
================================================
<template>
	<!-- Table -->
	<div
		v-if="doc?.expenses"
		class="flex flex-col bg-white mt-5 rounded border overflow-auto"
	>
		<div
			class="flex flex-row p-3.5 items-center justify-between cursor-pointer"
			v-for="(item, idx) in doc?.expenses"
			:key="idx"
		>
			<div class="flex flex-col w-full justify-center gap-2.5">
				<div class="flex flex-row items-center justify-between">
					<div class="flex flex-row items-start gap-3 grow">
						<div class="flex flex-col items-start gap-1.5">
							<div class="text-base font-normal text-gray-800">
								{{ __(item.expense_type) }}
							</div>
							<div class="text-xs font-normal text-gray-500">
								<span>
									{{
										__("{0}: {1}", [
											__("Sanctioned"),
											formatCurrency(item.sanctioned_amount || 0, currency),
										])
									}}
								</span>
								<span class="whitespace-pre"> &middot; </span>
								<span class="whitespace-nowrap" v-if="item.expense_date">
									{{ dayjs(item.expense_date).format("D MMM") }}
								</span>
							</div>
						</div>
					</div>
					<span class="text-gray-700 font-normal rounded text-base">
						{{ formatCurrency(item.amount, currency) }}
					</span>
				</div>
			</div>
		</div>
	</div>
</template>

<script setup>
import { computed, inject } from "vue"

import { getCompanyCurrency } from "@/data/currencies"
import { formatCurrency } from "@/utils/formatters"

const props = defineProps({
	doc: {
		type: Object,
		required: true,
	},
})

const dayjs = inject("$dayjs")
const currency = computed(() => getCompanyCurrency(props.doc.company))
</script>


================================================
FILE: frontend/src/components/ExpenseTaxesTable.vue
================================================
<template>
	<template v-if="expenseClaim.expenses">
		<div class="flex flex-row justify-between items-center pt-4">
			<h2 class="text-base font-semibold text-gray-800">{{ __("Taxes & Charges") }} </h2>
			<div class="flex flex-row gap-3 items-center">
				<span class="text-base font-semibold text-gray-800">
					{{ formatCurrency(expenseClaim.total_taxes_and_charges, currency) }}
				</span>
				<Button
					v-if="!isReadOnly"
					id="add-taxes-modal"
					class="text-sm"
					icon="plus"
					variant="subtle"
					@click="openModal()"
				/>
			</div>
		</div>

		<div
			v-if="expenseClaim.taxes?.length"
			class="flex flex-col bg-white mt-5 rounded border overflow-auto"
		>
			<div
				class="flex flex-row p-3.5 items-center justify-between border-b cursor-pointer"
				v-for="(item, idx) in expenseClaim.taxes"
				:key="item.name"
				@click="openModal(item, idx)"
			>
				<div class="flex flex-col w-full justify-center gap-2.5">
					<div class="flex flex-row items-center justify-between">
						<div class="flex flex-row items-start gap-3 grow">
							<div class="flex flex-col items-start gap-1.5">
								<div class="text-base font-normal text-gray-800">
									{{ item.account_head }}
								</div>
								<div class="text-xs font-normal text-gray-500">
									<span> Rate: {{ formatCurrency(item.rate, currency) }} </span>
									<span class="whitespace-pre"> &middot; </span>
									<span class="whitespace-nowrap">
										Amount: {{ formatCurrency(item.tax_amount, currency) }}
									</span>
								</div>
							</div>
						</div>
						<div class="flex flex-row justify-end items-center gap-2">
							<span class="text-gray-700 font-normal rounded text-base">
								{{ formatCurrency(item.total, currency) }}
							</span>
							<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
						</div>
					</div>
				</div>
			</div>
		</div>
		<EmptyState v-else :message="__('No taxes added')" :isTableField="true" />

		<CustomIonModal :isOpen="isModalOpen" @didDismiss="resetSelectedItem()">
			<template #actionSheet>
				<!-- Add Expense Tax Action Sheet -->
				<div
					class="bg-white w-full flex flex-col items-center justify-center pb-5"
				>
					<div class="w-full pt-8 pb-5 border-b text-center">
						<span class="text-gray-900 font-bold text-xl">
							{{ modalTitle }}
						</span>
					</div>
					<div
						class="w-full flex flex-col items-center justify-center gap-5 p-4"
					>
						<div class="flex flex-col w-full space-y-4">
							<FormField
								v-for="field in taxesTableFields.data"
								:key="field.fieldname"
								class="w-full"
								:label="__(field.label, null, 'Expense Claim Detail')"
								:fieldtype="field.fieldtype"
								:fieldname="field.fieldname"
								:options="field.options"
								:linkFilters="field.linkFilters"
								:hidden="field.hidden"
								:reqd="field.reqd"
								:readOnly="field.read_only || isReadOnly"
								:default="field.default"
								v-model="expenseTax[field.fieldname]"
							/>
						</div>

						<div
							v-if="!isReadOnly"
							class="flex w-full flex-row items-center justify-between gap-3"
						>
							<Button
								v-if="editingIdx !== null"
								class="border-red-600 text-red-600 py-5 text-sm"
								variant="outline"
								theme="red"
								@click="deleteExpenseTax()"
							>
								<template #prefix>
									<FeatherIcon name="trash" class="w-4" />
								</template>
								{{ __("Delete") }}
							</Button>
							<Button
								variant="solid"
								class="w-full py-5 text-sm disabled:bg-gray-700 disabled:text-white"
								@click="updateExpenseTax()"
								:disabled="addButtonDisabled"
							>
								<template #prefix>
									<FeatherIcon
										:name="editingIdx === null ? 'plus' : 'check'"
										class="w-4"
									/>
								</template>
								{{ editingIdx === null ? __("Add Tax") : __("Update Tax") }}
							</Button>
						</div>
					</div>
				</div>
			</template>
		</CustomIonModal>
	</template>
</template>

<script setup>
import { FeatherIcon, createResource } from "frappe-ui"
import { computed, ref, watch, inject } from "vue"

import FormField from "@/components/FormField.vue"
import EmptyState from "@/components/EmptyState.vue"
import CustomIonModal from "@/components/CustomIonModal.vue"

import { formatCurrency } from "@/utils/formatters"

const props = defineProps({
	expenseClaim: {
		type: Object,
		required: true,
	},
	currency: {
		type: String,
		required: true,
	},
	isReadOnly: {
		type: Boolean,
		default: false,
	},
})
const emit = defineEmits([
	"add-expense-tax",
	"update-expense-tax",
	"delete-expense-tax",
])
const __ = inject("$translate")
const expenseTax = ref({})
const editingIdx = ref(null)

const isModalOpen = ref(false)
const openModal = async (item, idx) => {
	if (item) {
		expenseTax.value = { ...item }
		editingIdx.value = idx
	}
	isModalOpen.value = true
}

const deleteExpenseTax = () => {
	emit("delete-expense-tax", editingIdx.value)
	resetSelectedItem()
}

const updateExpenseTax = () => {
	if (editingIdx.value === null) {
		emit("add-expense-tax", expenseTax.value)
	} else {
		emit("update-expense-tax", expenseTax.value, editingIdx.value)
	}
	resetSelectedItem()
}

function resetSelectedItem() {
	isModalOpen.value = false
	expenseTax.value = {}
	editingIdx.value = null
}

const taxesTableFields = createResource({
	url: "hrms.api.get_doctype_fields",
	params: { doctype: "Expense Taxes and Charges" },
	transform(data) {
		const excludeFields = ["description_sb"]
		return data
			.map((field) => {
				if (field.fieldname === "account_head") {
					field.linkFilters = {
						company: props.expenseClaim.company,
						account_type: [
							"in",
							[
								"Tax",
								"Chargeable",
								"Income Account",
								"Expenses Included In Valuation",
							],
						],
					}
				}
				return field
			})
			.filter((field) => !excludeFields.includes(field.fieldname))
	},
})
taxesTableFields.reload()

const modalTitle = computed(() => {
	if (props.isReadOnly) return __("Expense Tax")

	return editingIdx.value === null ? __("New Expense Tax") : __("Edit Expense Tax")
})

const addButtonDisabled = computed(() => {
	return taxesTableFields.data?.some((field) => {
		if (field.reqd && !expenseTax.value[field.fieldname]) {
			return true
		}
	})
})

// child table scripts
watch(
	() => expenseTax.value.account_head,
	(value) => {
		// set description from account head
		expenseTax.value.description = value?.split(" - ").slice(0, -1).join(" - ")
	}
)

watch(
	() => expenseTax.value.rate,
	(newVal, oldVal) => {
		if (editingIdx.value && newVal && !oldVal) return

		expenseTax.value.tax_amount =
			parseFloat(props.expenseClaim.total_sanctioned_amount) *
			(parseFloat(newVal) / 100)
		calculateTotalTax()
	}
)

watch(
	() => expenseTax.value.tax_amount,
	(_value) => {
		calculateTotalTax()
	}
)

function calculateTotalTax() {
	expenseTax.value.total =
		parseFloat(props.expenseClaim.total_sanctioned_amount) +
		parseFloat(expenseTax.value.tax_amount)
}
</script>


================================================
FILE: frontend/src/components/ExpensesTable.vue
================================================
<template>
	<!-- Header -->
	<div class="flex flex-row justify-between items-center mt-2">
		<h2 class="text-base font-semibold text-gray-800">{{ __("Expenses") }} </h2>
		<div class="flex flex-row gap-3 items-center">
			<span class="text-base font-semibold text-gray-800">
				{{ formatCurrency(expenseClaim.total_claimed_amount, currency) }}
			</span>
			<Button
				v-if="!isReadOnly"
				id="add-expense-modal"
				class="text-sm"
				icon="plus"
				variant="subtle"
				@click="openModal()"
			/>
		</div>
	</div>

	<!-- Table -->
	<div
		v-if="expenseClaim.expenses"
		class="flex flex-col bg-white mt-5 rounded border overflow-auto"
	>
		<div
			class="flex flex-row p-3.5 items-center justify-between border-b cursor-pointer"
			v-for="(item, idx) in expenseClaim.expenses"
			:key="idx"
			@click="openModal(item, idx)"
		>
			<div class="flex flex-col w-full justify-center gap-2.5">
				<div class="flex flex-row items-center justify-between">
					<div class="flex flex-row items-start gap-3 grow">
						<div class="flex flex-col items-start gap-1.5">
							<div class="text-base font-normal text-gray-800">
								{{ __(item.expense_type) }}
							</div>
							<div class="text-xs font-normal text-gray-500">
								<span>
									{{
										__("{0}: {1}", [
											__("Sanctioned"),
											formatCurrency(item.sanctioned_amount || 0, currency),
										])
									}}
								</span>
								<span class="whitespace-pre"> &middot; </span>
								<span class="whitespace-nowrap" v-if="item.expense_date">
									{{ dayjs(item.expense_date).format("D MMM") }}
								</span>
							</div>
						</div>
					</div>
					<div class="flex flex-row justify-end items-center gap-2">
						<span class="text-gray-700 font-normal rounded text-base">
							{{ formatCurrency(item.amount, currency) }}
						</span>
						<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
					</div>
				</div>
			</div>
		</div>
	</div>
	<EmptyState v-else :message="__('No expenses added')" :isTableField="true" />

	<CustomIonModal :isOpen="isModalOpen" @didDismiss="resetSelectedItem()">
		<template #actionSheet>
			<!-- Add Expense Action Sheet -->
			<div
				class="bg-white w-full flex flex-col items-center justify-center pb-5"
			>
				<div class="w-full pt-8 pb-5 border-b text-center">
					<span class="text-gray-900 font-bold text-lg">
						{{ modalTitle }}
					</span>
				</div>
				<div class="w-full flex flex-col items-center justify-center gap-5 p-4 max-h-[80vh]">
					<div class="flex flex-col w-full space-y-4 overflow-y-auto">
						<FormField
							v-for="field in expensesTableFields.data"
							:key="field.fieldname"
							class="w-full"
							:label="__(field.label, null, 'Expense Claim Detail')"
							:fieldtype="field.fieldtype"
							:fieldname="field.fieldname"
							:options="field.options"
							:hidden="field.hidden"
							:reqd="field.reqd"
							:default="field.default"
							:readOnly="field.read_only || isReadOnly"
							v-model="expenseItem[field.fieldname]"
						/>
					</div>

					<div
						v-if="!isReadOnly"
						class="flex w-full flex-row items-center justify-between gap-3"
					>
						<Button
							v-if="editingIdx !== null"
							class="border-red-600 text-red-600 py-5 text-sm"
							variant="outline"
							theme="red"
							@click="deleteExpenseItem()"
						>
							<template #prefix>
								<FeatherIcon name="trash" class="w-4" />
							</template>
							{{ __("Delete") }}
						</Button>
						<Button
							variant="solid"
							class="w-full py-5 text-sm disabled:bg-gray-700 disabled:text-white"
							@click="updateExpenseItem()"
							:disabled="addButtonDisabled"
						>
							<template #prefix>
								<FeatherIcon
									:name="editingIdx === null ? 'plus' : 'check'"
									class="w-4"
								/>
							</template>
							{{ editingIdx === null ? __("Add Expense") : __("Update Expense") }}
						</Button>
					</div>
				</div>
			</div>
		</template>
	</CustomIonModal>
</template>

<script setup>
import { FeatherIcon, createResource } from "frappe-ui"
import { computed, ref, watch, inject } from "vue"

import FormField from "@/components/FormField.vue"
import EmptyState from "@/components/EmptyState.vue"
import CustomIonModal from "@/components/CustomIonModal.vue"

import { claimTypesByID } from "@/data/claims"
import { formatCurrency } from "@/utils/formatters"

const props = defineProps({
	expenseClaim: {
		type: Object,
		required: true,
	},
	currency: {
		type: String,
		required: true,
	},
	isReadOnly: {
		type: Boolean,
		default: false,
	},
})
const emit = defineEmits([
	"add-expense-item",
	"update-expense-item",
	"delete-expense-item",
])
const dayjs = inject("$dayjs")
const __ = inject("$translate")
const expenseItem = ref({})
const editingIdx = ref(null)

const isModalOpen = ref(false)
const isFirstRender = ref(false)

const openModal = async (item, idx) => {
	if (item) {
		expenseItem.value = { ...item }
		editingIdx.value = idx
	}
	isFirstRender.value = true
	isModalOpen.value = true
}

const deleteExpenseItem = () => {
	emit("delete-expense-item", editingIdx.value)
	resetSelectedItem()
}

const updateExpenseItem = () => {
	if (editingIdx.value === null) {
		emit("add-expense-item", expenseItem.value)
	} else {
		emit("update-expense-item", expenseItem.value, editingIdx.value)
	}
	resetSelectedItem()
}

function resetSelectedItem() {
	isFirstRender.value = false
	isModalOpen.value = false
	expenseItem.value = {}
	editingIdx.value = null
}

const expensesTableFields = createResource({
	url: "hrms.api.get_doctype_fields",
	params: { doctype: "Expense Claim Detail" },
	transform(data) {
		const excludeFields = ["description_sb", "amounts_sb"]
		return data.filter((field) => !excludeFields.includes(field.fieldname))
	},
})
expensesTableFields.reload()

const modalTitle = computed(() => {
	if (props.isReadOnly) return __("Expense Item")

	return editingIdx.value === null ? __("New Expense Item") : __("Edit Expense Item")
})

const addButtonDisabled = computed(() => {
	return expensesTableFields.data?.some((field) => {
		if (field.reqd && !expenseItem.value[field.fieldname]) {
			return true
		}
	})
})

// child table form scripts
watch(
	() => expenseItem.value.expense_type,
	(value) => {
		if (!expenseItem.value.description) {
			expenseItem.value.description = claimTypesByID[value]?.description
		}

		expenseItem.value.cost_center = props.expenseClaim.cost_center
	}
)

watch(
	() => expenseItem.value.amount,
	(value) => {
		if (!isFirstRender.value) {
			expenseItem.value.sanctioned_amount = parseFloat(value)
		} else {
			isFirstRender.value = false
		}
	}
)
</script>


================================================
FILE: frontend/src/components/FilePreviewModal.vue
================================================
<template>
	<ion-header>
		<ion-toolbar>
			<ion-title>{{ filename }} - {{ __("File Preview") }}</ion-title>
			<ion-buttons slot="end">
				<ion-button @click="modalController.dismiss()">{{ __("Close") }} </ion-button>
			</ion-buttons>
		</ion-toolbar>
	</ion-header>
	<ion-content>
		<div class="bg-white h-full w-full overflow-auto touch-pinch-zoom">
			<img v-if="isImageFile" :src="src" class="h-auto image-preview" />
			<iframe v-else :src="src" class="w-full h-full"></iframe>
		</div>
	</ion-content>
</template>

<script setup>
import { computed, onBeforeUnmount } from "vue"
import {
	IonHeader,
	IonToolbar,
	IonContent,
	IonButtons,
	IonTitle,
	IonButton,
	modalController,
} from "@ionic/vue"

const props = defineProps({
	file: {
		type: Object,
		required: true,
	},
})

const filename = computed(() => {
	return props.file.file_name || props.file.name
})

const src = computed(() => {
	return props.file.file_url
		? props.file.file_url
		: URL.createObjectURL(props.file)
})

const isImageFile = computed(() => {
	return /\.(gif|jpg|jpeg|tiff|png|svg)$/i.test(filename.value)
})

onBeforeUnmount(() => {
	URL.revokeObjectURL(src.value)
})
</script>

<style scoped>
.image-preview {
	image-orientation: from-image;
}
</style>


================================================
FILE: frontend/src/components/FileUploaderView.vue
================================================
<template>
	<div class="flex flex-col gap-3 py-4">
		<label class="file-select">
			<h2 class="text-base font-semibold text-gray-800 pb-4">{{ __("Attachments") }} </h2>
			<div class="select-button cursor-pointer">
				<div
					class="flex flex-col w-full border shadow-sm items-center rounded p-3 gap-2"
				>
					<FeatherIcon name="upload" class="h-6 w-6 text-gray-700" />
					<span class="block text-sm font-normal leading-5 text-gray-700">
						{{ __("Upload images or documents") }}
					</span>
				</div>
				<input
					class="hidden"
					ref="input"
					type="file"
					multiple
					accept="*"
					@change="(e) => emit('handle-file-select', e)"
				/>
			</div>
		</label>

		<div v-if="modelValue.length" class="w-full">
			<ul class="w-full flex flex-col items-center gap-2">
				<li
					class="bg-gray-100 rounded p-2 w-full"
					v-for="(file, index) in modelValue"
					:key="index"
				>
					<div
						class="flex flex-row items-center justify-between text-gray-700 text-sm"
					>
						<span class="grow" @click="showFilePreview(file)">
							{{ file.file_name || file.name }}
						</span>
						<FeatherIcon
							name="x"
							class="h-4 w-4 cursor-pointer text-gray-700"
							@click="() => confirmDeleteAttachment(file)"
						/>
					</div>
				</li>
			</ul>

			<Dialog v-model="showDialog">
				<template #body-title>
					<h2 class="text-lg font-bold">{{ __("Delete Attachment") }} </h2>
				</template>
				<template #body-content>
					<p>
						{{ __("Are you sure you want to delete the attachment") }}
						<span class="font-bold">{{ selectedFile.file_name }}</span>
						?
					</p>
				</template>
				<template #actions>
					<div class="flex flex-row gap-4">
						<Button
							variant="outline"
							class="py-5 w-full"
							@click="showDialog = false"
						>
							{{ __("Cancel") }}
						</Button>
						<Button
							variant="solid"
							theme="red"
							@click="handleFileDelete"
							class="py-5 w-full"
						>
							{{ __("Delete") }}
						</Button>
					</div>
				</template>
			</Dialog>

			<!-- File Preview Modal -->
			<ion-modal
				ref="modal"
				:is-open="showPreviewModal"
				@didDismiss="showPreviewModal = false"
			>
				<FilePreviewModal :file="selectedFile" />
			</ion-modal>
		</div>
	</div>
</template>

<script setup>
import { FeatherIcon, Dialog } from "frappe-ui"
import { ref } from "vue"
import { IonModal } from "@ionic/vue"

import FilePreviewModal from "@/components/FilePreviewModal.vue"

const props = defineProps({
	modelValue: {
		type: Object,
		required: true,
	},
})
let showDialog = ref(false)
let showPreviewModal = ref(false)
let selectedFile = ref({})

const emit = defineEmits(["handle-file-select", "handle-file-delete"])

function showFilePreview(fileObj) {
	selectedFile.value = fileObj
	showPreviewModal.value = true
}

function confirmDeleteAttachment(fileObj) {
	selectedFile.value = fileObj
	showDialog.value = true
}

function handleFileDelete() {
	emit("handle-file-delete", selectedFile.value)
	showDialog.value = false
}
</script>

<style scoped>
ion-modal {
	--height: 100%;
}
</style>


================================================
FILE: frontend/src/components/FormField.vue
================================================
<template>
	<div v-if="showField" class="flex flex-col gap-1.5">
		<!-- Label -->
		<span
			v-if="!['Check', 'Section Break', 'Column Break'].includes(props.fieldtype)"
			:class="[
				// mark field as mandatory
				props.reqd ? `after:content-['_*'] after:text-red-600` : ``,
				`block text-sm leading-5 text-gray-700`,
			]"
		>
			{{ props.label }}
		</span>

		<!-- Select or Link field with predefined options -->
		<Autocomplete
			v-if="props.fieldtype === 'Select' || props.documentList"
			:class="isReadOnly ? 'pointer-events-none' : ''"
			:placeholder="__('Select {0}', [props.label])"
			:options="selectionList"
			:modelValue="modelValue"
			v-bind="$attrs"
			:disabled="isReadOnly"
			@update:modelValue="(v) => emit('update:modelValue', v?.value)"
		/>

		<!-- Link field -->
		<Link
			v-else-if="props.fieldtype === 'Link'"
			:doctype="props.options"
			:modelValue="modelValue"
			:filters="props.linkFilters"
			:disabled="isReadOnly"
			@update:modelValue="(v) => emit('update:modelValue', v)"
		/>

		<!-- Text -->
		<Input
			v-else-if="['Text Editor', 'Small Text', 'Text', 'Long Text'].includes(props.fieldtype)"
			type="textarea"
			:value="modelValue"
			:placeholder="__('Enter {0}', [props.label])"
			@input="(v) => emit('update:modelValue', v)"
			@change="(v) => emit('change', v)"
			v-bind="$attrs"
			:disabled="isReadOnly"
			class="h-15"
		/>

		<!-- Check -->
		<Input
			v-else-if="props.fieldtype === 'Check'"
			type="checkbox"
			:label="props.label"
			:value="modelValue"
			@input="(v) => emit('update:modelValue', v)"
			@change="(v) => emit('change', v)"
			v-bind="$attrs"
			:disabled="isReadOnly"
			class="rounded-sm text-gray-800"
		/>

		<!-- Data field -->
		<Input
			v-else-if="props.fieldtype === 'Data'"
			type="text"
			:value="modelValue"
			@input="(v) => emit('update:modelValue', v)"
			@change="(v) => emit('change', v)"
			v-bind="$attrs"
			:disabled="isReadOnly"
		/>

		<!-- Read only currency field -->
		<Input
			v-else-if="props.fieldtype === 'Currency' && isReadOnly"
			type="text"
			:value="modelValue"
			@input="(v) => emit('update:modelValue', v)"
			@change="(v) => emit('change', v)"
			v-bind="$attrs"
			:disabled="isReadOnly"
		/>

		<!-- Float/Int field -->
		<Input
			v-else-if="isNumberType"
			type="number"
			:value="modelValue"
			@input="(v) => emit('update:modelValue', v)"
			@change="(v) => emit('change', v)"
			v-bind="$attrs"
			:disabled="isReadOnly"
		/>

		<!-- Section Break -->
		<div
			v-else-if="props.fieldtype === 'Section Break'"
			:class="props.addSectionPadding ? 'mt-2' : ''"
		>
			<h2
				v-if="props.label"
				class="text-base font-semibold text-gray-800"
				:class="props.addSectionPadding ? 'pt-4' : ''"
			>
				{{ props.label }}
			</h2>
		</div>

		<!-- Date -->
		<!-- FIXME: default datepicker has poor UI -->
		<Input
			v-else-if="props.fieldtype === 'Date'"
			type="date"
			:value="modelValue"
			:placeholder="__('Select {0}', [props.label])"
			:formatValue="(val) => dayjs(val).format('DD-MM-YYYY')"
			@input="(v) => emit('update:modelValue', v)"
			@change="(v) => emit('change', v)"
			v-bind="$attrs"
			:disabled="isReadOnly"
			:min="props.minDate"
			:max="props.maxDate"
		/>

		<!-- Time -->
		<!-- Datetime -->
		<DateTimePicker
			v-else-if="props.fieldtype === 'Datetime'"
			:value="modelValue"
			:placeholder="`Select ${props.label}`"
			:formatter="(val) => dayjs(val).format('DD-MM-YYYY HH:mm:ss')"
			@update:modelValue="(v) => emit('update:modelValue', v)"
			v-bind="$attrs"
			:disabled="isReadOnly"
		/>

		<ErrorMessage :message="props.errorMessage" />
	</div>
</template>

<script setup>
import { Autocomplete, DateTimePicker, ErrorMessage, Input } from "frappe-ui"
import { computed, onMounted, inject } from "vue"

import Link from "@/components/Link.vue"

const __ = inject("$translate")

const props = defineProps({
	fieldtype: String,
	fieldname: String,
	modelValue: [String, Number, Boolean, Array, Object],
	default: [String, Number, Boolean, Array, Object],
	label: String,
	options: [String, Array],
	linkFilters: Object,
	documentList: Array,
	readOnly: [Boolean, Number],
	reqd: [Boolean, Number],
	hidden: {
		type: [Boolean, Number],
		default: false,
	},
	errorMessage: String,
	minDate: String,
	maxDate: String,
	addSectionPadding: {
		type: Boolean,
		default: true,
	},
})

const emit = defineEmits(["change", "update:modelValue"])
const dayjs = inject("$dayjs")

const showField = computed(() => {
	if (props.readOnly && !isLayoutField.value && !props.modelValue) return false

	return props.fieldtype !== "Table" && !props.hidden
})

const isNumberType = computed(() => {
	return ["Int", "Float", "Currency"].includes(props.fieldtype)
})

const isLayoutField = computed(() => {
	return ["Section Break", "Column Break"].includes(props.fieldtype)
})

const isReadOnly = computed(() => {
	return Boolean(props.readOnly)
})

const selectionList = computed(() => {
	if (props.fieldtype === "Link" && props.documentList) {
		return props.documentList
	} else if (props.fieldtype == "Select" && props.options) {
		const options = props.options.split("\n")
		return options.map((option) => ({
			label: __(option),
			value: option,
		}))
	}

	return []
})

function setDefaultValue() {
	// set default values
	if (props.modelValue) return

	if (props.default) {
		if (props.fieldtype === "Check") {
			emit("update:modelValue", props.default === "1" ? true : false)
		} else if (props.fieldtype === "Date" && props.default === "Today") {
			emit("update:modelValue", dayjs().format("YYYY-MM-DD"))
		} else if (isNumberType.value) {
			emit("update:modelValue", parseFloat(props.default || 0))
		} else {
			emit("update:modelValue", props.default)
		}
	} else {
		props.fieldtype === "Check" ? emit("update:modelValue", false) : emit("update:modelValue", "")
	}
}

onMounted(() => {
	setDefaultValue()
})
</script>


================================================
FILE: frontend/src/components/FormView.vue
================================================
<template>
	<div class="flex flex-col h-full w-full" v-if="isFormReady">
		<div class="w-full h-full bg-white sm:w-96 flex flex-col">
			<header
				class="flex flex-row bg-white shadow-sm py-4 px-3 items-center sticky top-0 z-[1000]"
			>
				<Button
					variant="ghost"
					class="!pl-0 hover:bg-white"
					@click="router.back()"
				>
					<FeatherIcon name="chevron-left" class="h-5 w-5" />
				</Button>
				<div
					v-if="id"
					class="flex flex-row items-center gap-2 overflow-hidden grow"
				>
					<h2
						class="text-xl font-semibold text-gray-900 whitespace-nowrap overflow-hidden text-ellipsis"
					>
						{{ __(props.doctype) }}
					</h2>
					<Badge
						:label="id"
						class="whitespace-nowrap text-[8px]"
						variant="outline"
					/>
					<Badge
						v-if="status"
						:label="__(status, null, doctype)"
						:theme="statusColor"
						class="whitespace-nowrap text-[8px]"
					/>

					<Dropdown
						class="ml-auto"
						:options="[
							{
								label: __('Delete'),
								condition: showDeleteButton,
								onClick: () => (showDeleteDialog = true),
							},
							{ label: __('Reload'), onClick: () => reloadDoc() },
							{
								label: __('Download PDF'),
								condition: () => props.showDownloadPDFButton,
								onClick: () => (handleDownload()),
							},
						]"
						:button="{
							label: __('Menu'),
							icon: 'more-horizontal',
							variant: 'ghost',
						}"
					/>
				</div>
				<h2 v-else class="text-2xl font-semibold text-gray-900">
					{{ __('New {0}', [__(doctype)], props.doctype) }}
				</h2>
			</header>

			<!-- Form -->
			<div class="bg-white grow overflow-y-auto">
				<!-- Tabs -->
				<template v-if="tabbedView">
					<div
						class="px-4 sticky top-0 z-[100] bg-white text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700"
					>
						<ul class="flex -mb-px overflow-auto hide-scrollbar">
							<li class="mr-2 whitespace-nowrap" v-for="tab in tabs">
								<button
									@click="activeTab = tab.name"
									class="inline-block py-4 px-2 border-b-2 border-transparent rounded-t-lg"
									:class="[
										activeTab === tab.name
											? '!text-gray-800 !border-gray-800'
											: 'hover:text-gray-600 hover:border-gray-300',
									]"
								>
									{{ __(tab.name, null, props.doctype) }}
								</button>
							</li>
						</ul>
					</div>

					<template v-for="(fieldList, tabName, index) in tabFields">
						<div
							v-show="tabName === activeTab"
							class="flex flex-col space-y-4 p-4"
						>
							<template v-for="field in fieldList" :key="field.fieldname">
								<slot
									v-if="field.fieldtype == 'Table'"
									:name="field.fieldname"
									:isFormReadOnly="isFormReadOnly"
								></slot>

								<FormField
									v-else
									:fieldtype="field.fieldtype"
									:fieldname="field.fieldname"
									v-model="formModel[field.fieldname]"
									:default="field.default"
									:label="__(field.label, null, props.doctype)"
									:options="field.options"
									:linkFilters="field.linkFilters"
									:documentList="field.documentList"
									:readOnly="isFieldReadOnly(field)"
									:reqd="Boolean(field.reqd)"
									:hidden="Boolean(field.hidden)"
									:errorMessage="field.error_message"
									:minDate="field.minDate"
									:maxDate="field.maxDate"
									:addSectionPadding="fieldList[0].name !== field.name"
								/>
							</template>

							<!-- Attachment upload -->
							<div
								class="flex flex-row gap-2 items-center justify-center p-5"
								v-if="isFileUploading"
							>
								<LoadingIndicator class="w-3 h-3 text-gray-800" />
								<span class="text-gray-900 text-sm">{{ __("Uploading...") }} </span>
							</div>

							<FileUploaderView
								v-else-if="showAttachmentView && index === 0"
								v-model="fileAttachments"
								@handleFileSelect="handleFileSelect"
								@handleFileDelete="handleFileDelete"
							/>
						</div>
					</template>
				</template>

				<div class="flex flex-col space-y-4 p-4" v-else>
					<FormField
						v-for="field in props.fields"
						:key="field.name"
						:fieldtype="field.fieldtype"
						:fieldname="field.fieldname"
						v-model="formModel[field.fieldname]"
						:default="field.default"
						:label="__(field.label, null, props.doctype)"
						:options="field.options"
						:linkFilters="field.linkFilters"
						:documentList="field.documentList"
						:readOnly="isFieldReadOnly(field)"
						:reqd="Boolean(field.reqd)"
						:hidden="Boolean(field.hidden)"
						:errorMessage="field.error_message"
						:minDate="field.minDate"
						:maxDate="field.maxDate"
					/>

					<!-- Attachment upload -->
					<div
						class="flex flex-row gap-2 items-center justify-center p-5"
						v-if="isFileUploading"
					>
						<LoadingIndicator class="w-3 h-3 text-gray-800" />
						<span class="text-gray-900 text-sm">{{ __("Uploading...") }} </span>
					</div>

					<FileUploaderView
						v-else-if="showAttachmentView"
						v-model="fileAttachments"
						@handleFileSelect="handleFileSelect"
						@handleFileDelete="handleFileDelete"
					/>
				</div>
			</div>

			<!-- Form Primary/Secondary Button -->
			<!-- custom form button eg: Download button in salary slips -->
			<div
				v-if="!showFormButton"
				class="px-4 pt-4 pb-4 standalone:pb-safe-bottom sm:w-96 bg-white sticky bottom-0 w-full drop-shadow-xl z-40 border-t rounded-t-lg"
			>
				<slot name="formButton"></slot>
			</div>

			<!-- workflow actions -->
			<WorkflowActionSheet
				v-else-if="!isFormDirty && workflow?.hasWorkflow"
				:doc="documentResource.doc"
				:workflow="workflow"
				@workflowApplied="reloadDoc()"
			/>

			<!-- save/submit/cancel -->
			<div
				v-else-if="isFormDirty || (!workflow?.hasWorkflow && formButton)"
				class="px-4 pt-4 pb-4 standalone:pb-safe-bottom sm:w-96 bg-white sticky bottom-0 w-full drop-shadow-xl z-40 border-t rounded-t-lg"
			>
				<ErrorMessage
					class="mb-2"
					:message="
						formErrorMessage ||
						docList?.insert?.error ||
						documentResource?.setValue?.error
					"
				/>

				<Button
					class="w-full rounded py-5 text-base disabled:bg-gray-700 disabled:text-white"
					:class="formButton === 'Cancel' ? 'shadow' : ''"
					@click="formButton === 'Save' ? saveForm() : submitOrCancelForm()"
					:variant="formButton === 'Cancel' ? 'subtle' : 'solid'"
					:loading="
						docList.insert.loading || documentResource?.setValue?.loading
					"
				>
					{{ __(formButton) }}
				</Button>
			</div>
		</div>
	</div>

	<!-- Confirmation Dialogs -->
	<Dialog v-model="showDeleteDialog">
		<template #body-title>
			<h2 class="text-xl font-bold">{{ __("Delete {0}", [__(props.doctype)]) }}</h2>
		</template>
		<template #body-content>
			<p>
				{{ __("Are you sure you want to delete the {0}", [__(props.doctype)])  }}
				<span class="font-bold">{{ formModel.name }}</span>
				?
			</p>
		</template>
		<template #actions>
			<div class="flex flex-row gap-4">
				<Button
					variant="outline"
					class="py-5 w-full"
					@click="showDeleteDialog = false"
				>
					{{ __("Cancel") }}
				</Button>
				<Button
					variant="solid"
					theme="red"
					@click="handleDocDelete"
					class="py-5 w-full"
				>
					{{__("Delete") }}
				</Button>
			</div>
		</template>
	</Dialog>

	<Dialog v-model="showSubmitDialog">
		<template #body-title>
			<h2 class="text-xl font-bold">{{ __("Confirm") }} </h2>
		</template>
		<template #body-content>
			<p>
				{{ __("Permanently submit {0}", [__(props.doctype)]) }}
				<span class="font-bold">{{ formModel.name }}</span>
				?
			</p>
		</template>
		<template #actions>
			<div class="flex flex-row gap-4">
				<Button
					variant="outline"
					class="py-5 w-full"
					@click="showSubmitDialog = false"
				>
					{{ __("No") }}
				</Button>
				<Button
					variant="solid"
					@click="handleDocUpdate('submit')"
					class="py-5 w-full"
				>
					{{ __("Yes") }}
				</Button>
			</div>
		</template>
	</Dialog>

	<Dialog v-model="showCancelDialog">
		<template #body-title>
			<h2 class="text-xl font-bold">{{ __("Confirm") }} </h2>
		</template>
		<template #body-content>
			<p>
				{{ __("Permanently cancel {0}", [__(props.doctype)]) }}
				<span class="font-bold">{{ formModel.name }}</span
				>?
			</p>
		</template>
		<template #actions>
			<div class="flex flex-row gap-4">
				<Button
					variant="outline"
					class="py-5 w-full"
					@click="showCancelDialog = false"
				>
					{{ __("No") }}
				</Button>
				<Button
					variant="solid"
					@click="handleDocUpdate('cancel')"
					class="py-5 w-full"
				>
					{{ __("Yes") }}
				</Button>
			</div>
		</template>
	</Dialog>
</template>

<script setup>
import { computed, inject, nextTick, onMounted, ref, watch } from "vue"
import { useRouter } from "vue-router"
import {
	ErrorMessage,
	Badge,
	FeatherIcon,
	createListResource,
	createDocumentResource,
	toast,
	createResource,
	Dropdown,
	Dialog,
	LoadingIndicator,
} from "frappe-ui"
import FormField from "@/components/FormField.vue"
import FileUploaderView from "@/components/FileUploaderView.vue"
import WorkflowActionSheet from "@/components/WorkflowActionSheet.vue"

import { FileAttachment, guessStatusColor } from "@/composables"
import useWorkflow from "@/composables/workflow"
import { getCompanyCurrency } from "@/data/currencies"
import { formatCurrency } from "@/utils/formatters"
import { useDownloadPDF } from "@/utils/commonUtils"

const props = defineProps({
	doctype: {
		type: String,
		required: true,
	},
	modelValue: {
		type: Object,
		required: true,
	},
	isSubmittable: {
		type: Boolean,
		required: false,
		default: false,
	},
	fields: {
		type: Array,
		required: true,
	},
	id: {
		type: String,
		required: false,
	},
	tabbedView: {
		type: Boolean,
		required: false,
		default: false,
	},
	tabs: {
		type: Array,
		required: false,
	},
	showAttachmentView: {
		type: Boolean,
		required: false,
		default: false,
	},
	showFormButton: {
		type: Boolean,
		required: false,
		default: true,
	},
	showDownloadPDFButton: {
		type: Boolean,
		required: false,
		default: false,
	},
})
const emit = defineEmits(["validateForm", "update:modelValue"])
const router = useRouter()
const { downloadPDF } = useDownloadPDF()

const __ = inject("$translate")

let activeTab = ref(props.tabs?.[0].name)
let fileAttachments = ref([])
let statusColor = ref("")
let formErrorMessage = ref("")
let isFormDirty = ref(false)
let isFormUpdated = ref(false)
let showDeleteDialog = ref(false)
let showSubmitDialog = ref(false)
let showCancelDialog = ref(false)
let isFileUploading = ref(false)
let workflow = ref(null)

const formModel = computed({
	get() {
		return props.modelValue
	},
	set(newValue) {
		emit("update:modelValue", newValue)
	},
})

const status = computed(() => {
	if (!props.id) return ""

	if (workflow.value) {
		const stateField = workflow.value.getWorkflowStateField()
		if (stateField) return formModel.value[stateField]
	}

	return formModel.value.status || formModel.value.approval_status
})

watch(
	() => formModel.value,
	() => {
		if (!props.id) return

		if (isFormReady.value && !isFormUpdated.value) {
			isFormDirty.value = true
		} else if (isFormUpdated.value) {
			isFormUpdated.value = false
		}
	},
	{ deep: true }
)

watch(
	() => status.value,
	async (value) => {
		if (!value) return
		statusColor.value = await guessStatusColor(props.doctype, status.value)
	},
	{ immediate: true }
)

const tabFields = computed(() => {
	let fieldsByTab = {}
	let fieldList = []
	let firstFieldIndex = 0
	let lastFieldIndex = 0

	props.tabs?.forEach((tab) => {
		lastFieldIndex = props.fields.findIndex(
			(field) => field.fieldname === tab.lastField
		)
		fieldList = props.fields.slice(firstFieldIndex, lastFieldIndex + 1)
		fieldsByTab[tab.name] = fieldList
		firstFieldIndex = lastFieldIndex + 1
	})

	return fieldsByTab
})

const attachedFiles = createResource({
	url: "hrms.api.get_attachments",
	params: {
		dt: props.doctype,
		dn: props.id,
	},
	transform(data) {
		return data.map((file) => (file.uploaded = true))
	},
	onSuccess(data) {
		fileAttachments.value = data
	},
})

const handleFileSelect = (e) => {
	if (props.id) {
		uploadAllAttachments(props.doctype, props.id, [...e.target.files])
	} else {
		fileAttachments.value.push(...e.target.files)
	}
}

const handleFileDelete = async (fileObj) => {
	if (fileObj.uploaded) {
		const fileAttachment = new FileAttachment(fileObj)
		await fileAttachment.delete()
		await attachedFiles.reload()
	} else {
		fileAttachments.value = fileAttachments.value.filter(
			(file) => file.name !== fileObj.name
		)
	}
}

async function uploadAllAttachments(documentType, documentName, attachments) {
	isFileUploading.value = true

	const uploadPromises = attachments.map((attachment) => {
		const fileAttachment = new FileAttachment(attachment)
		return fileAttachment
			.upload(documentType, documentName, "")
			.then((fileDoc) => {
				fileDoc.uploaded = true
				if (props.id) {
					fileAttachments.value.push(fileDoc)
				}
			})
	})

	await Promise.allSettled(uploadPromises)
	isFileUploading.value = false
}

// CRUD for doc
const docList = createListResource({
	doctype: props.doctype,
	insert: {
		async onSuccess(data) {
			toast({
				title: __("Success"),
				text: __("{0} created successfully!", [__(props.doctype)]),
				icon: "check-circle",
				position: "bottom-center",
				iconClasses: "text-green-500",
			})
			await uploadAllAttachments(data.doctype, data.name, fileAttachments.value)

			router.replace({
				name: `${props.doctype.replace(/\s+/g, "")}DetailView`,
				params: { id: data.name },
			})
		},
		onError() {
			toast({
				title: __("Error"),
				text: __("Error creating {0}", [__(props.doctype)]),
				icon: "alert-circle",
				position: "bottom-center",
				iconClasses: "text-red-500",
			})
			console.log(`Error creating ${props.doctype}`)
		},
	},
})

const documentResource = createDocumentResource({
	doctype: props.doctype,
	name: props.id,
	fields: "*",
	setValue: {
		onSuccess() {
			toast({
				title: __("Success"),
				text: __("{0} updated successfully!", [__(props.doctype)]),
				icon: "check-circle",
				position: "bottom-center",
				iconClasses: "text-green-500",
			})
		},
		onError() {
			toast({
				title: __("Error"),
				text: __("Error updating {0}", [__(props.doctype)]),
				icon: "alert-circle",
				position: "bottom-center",
				iconClasses: "text-red-500",
			})
			console.log(`Error updating ${props.doctype}`)
		},
	},
	delete: {
		onSuccess() {
			router.back()
			toast({
				title: __("Success"),
				text: __("{0} deleted successfully!", [__(props.doctype)]),
				icon: "check-circle",
				position: "bottom-center",
				iconClasses: "text-green-500",
			})
		},
		onError() {
			toast({
				title: __("Error"),
				text: __("Error deleting {0}", [__(props.doctype)]),
				icon: "alert-circle",
				position: "bottom-center",
				iconClasses: "text-red-500",
			})
			console.log(`Error deleting ${props.doctype}`)
		},
	},
})

const docPermissions = createResource({
	url: "frappe.client.get_doc_permissions",
	params: { doctype: props.doctype, docname: props.id },
})

const permittedWriteFields = createResource({
	url: "hrms.api.get_permitted_fields_for_write",
	params: { doctype: props.doctype },
})

const formButton = computed(() => {
	if (!props.showFormButton) return

	if (props.id && props.isSubmittable && !isFormDirty.value) {
		if (formModel.value.docstatus === 0 && hasPermission("submit")) {
			return "Submit"
		} else if (formModel.value.docstatus === 1 && hasPermission("cancel")) {
			return "Cancel"
		}
	} else if (formModel.value.docstatus !== 2) {
		return "Save"
	}
})

function showDeleteButton() {
	return props.id && formModel.value.docstatus !== 1 && hasPermission("delete")
}

function hasPermission(action) {
	return docPermissions.data?.permissions[action]
}

function isFieldReadOnly(field) {
	return (
		Boolean(field.read_only)
		|| isFormReadOnly.value
		|| (props.id && !permittedWriteFields.data?.includes(field.fieldname))
	)
}

function handleDocInsert() {
	if (!validateMandatoryFields()) return
	docList.insert.submit(formModel.value)
}

function validateMandatoryFields() {
	const errorFields = props.fields
		.filter(
			(field) =>
				field.reqd && !field.hidden && !formModel.value[field.fieldname]
		)
		.map((field) => field.label)

	if (errorFields.length) {
		formErrorMessage.value = `${errorFields.join(", ")} ${
			errorFields.length > 1 ? "fields are mandatory" : "field is mandatory"
		}`
		return false
	} else {
		formErrorMessage.value = ""
		return true
	}
}

async function handleDocUpdate(action) {
	if (documentResource.doc) {
		let params = { ...formModel.value }

		if (!validateMandatoryFields()) return

		if (action == "submit") {
			params.docstatus = 1
		} else if (action == "cancel") {
			params.docstatus = 2
		}
		
		await documentResource.setValue.submit(params)
		await documentResource.get.promise
		resetForm()
	}

	if (action === "submit") showSubmitDialog.value = false
	else if (action === "cancel") showCancelDialog.value = false
}

function saveForm() {
	emit("validateForm")

	if (props.id) {
		handleDocUpdate()
	} else {
		handleDocInsert()
	}
}

function submitOrCancelForm() {
	if (isFormDirty.value) return

	if (formModel.value.docstatus === 0) {
		emit("validateForm")
		showSubmitDialog.value = true
	} else if (formModel.value.docstatus === 1) {
		showCancelDialog.value = true
	}
}

function handleDocDelete() {
	documentResource.delete.submit()
	showDeleteDialog.value = false
}

async function reloadDoc() {
	await documentResource.reload()
	resetForm()
}

function resetForm() {
	formModel.value = { ...documentResource.doc }
	nextTick(() => {
		isFormDirty.value = false
		isFormUpdated.value = true
	})
}
function handleDownload() {
	if (!props.id) return
	downloadPDF({
		doctype: props.doctype,
		docname: props.id,
		filename: props.id,
	})
}

async function setFormattedCurrency() {
	const companyCurrency = await getCompanyCurrency(formModel.value.company)

	props.fields.forEach((field) => {
		if (field.fieldtype !== "Currency") return
		if (!(field.readOnly || isFormReadOnly.value)) return

		if (field.options === "currency") {
			formModel.value[field.fieldname] = formatCurrency(
				formModel.value[field.fieldname],
				formModel.value.currency
			)
		} else {
			formModel.value[field.fieldname] = formatCurrency(
				formModel.value[field.fieldname],
				companyCurrency
			)
		}
	})
}

const isFormReadOnly = computed(() => {
	if (!isFormReady.value) return true
	if (!props.id) return false

	// submitted & cancelled docs are read only
	if (formModel.value.docstatus !== 0) return true

	// read only due to workflow based on current user's roles
	if (workflow.value?.isReadOnly(formModel.value)) return true
})

const isFormReady = computed(() => {
	if (!props.id) return true

	return !documentResource.get.loading && documentResource.doc
})

onMounted(async () => {
	if (props.id) {
		await documentResource.get.promise
		formModel.value = { ...documentResource.doc }
		await docPermissions.reload()
		await permittedWriteFields.reload()
		await attachedFiles.reload()
		await setFormattedCurrency()

		// workflow
		workflow.value = useWorkflow(props.doctype)

		isFormDirty.value = false
	}
})
</script>


================================================
FILE: frontend/src/components/FormattedField.vue
================================================
<template>
	<div v-if="!props.value" class="text-gray-600 text-base">-</div>

	<Badge
		v-else-if="props.fieldtype === 'Select'"
		variant="outline"
		:theme="colorMap[props.value]"
		:label="__(props.value)"
		size="md"
	/>

	<div v-else-if="props.fieldtype === 'Date'" class="text-gray-900 text-base">
		{{ dayjs(props.value).format("D MMM YYYY") }}
	</div>

	<Input
		v-else-if="props.fieldtype === 'Check'"
		type="checkbox"
		label=""
		v-model="props.value"
		:disabled="true"
		class="rounded-sm text-gray-800"
	/>

	<div
		v-else-if="['Small Text', 'Text', 'Long Text'].includes(props.fieldtype)"
		class="text-gray-900 text-base bg-gray-100 rounded py-3 pl-3 mt-2"
	>
		{{ props.value }}
	</div>

	<EmployeeAvatar
		v-else-if="props.fieldtype === 'Link' && ['employee', 'reports_to'].includes(props.fieldname)"
		:employeeID="props.value"
		:showLabel="true"
	/>

	<div
		v-else-if="props.fieldtype === 'geolocation'"
		class="rounded border-4 translate-z-0 block overflow-hidden w-full h-170 mt-2"
	>
		<iframe
			width="100%"
			height="170"
			frameborder="0"
			scrolling="no"
			marginheight="0"
			marginwidth="0"
			style="border: 0"
			:src="`https://maps.google.com/maps?q=${getCoordinates(props.value).latitude},${
				getCoordinates(props.value).longitude
			}&hl=en&z=15&amp;output=embed`"
		>
		</iframe>
	</div>

	<div v-else class="text-gray-900 text-base">{{ props.value }}</div>
</template>

<script setup>
import { inject } from "vue"
import { Badge, FormControl, Input } from "frappe-ui"

import EmployeeAvatar from "@/components/EmployeeAvatar.vue"

const dayjs = inject("$dayjs")

const props = defineProps({
	value: [String, Number, Boolean, Array, Object],
	fieldtype: String,
	fieldname: String,
})

const colorMap = {
	Approved: "green",
	Rejected: "red",
	Open: "orange",
}

const getCoordinates = (value) => {
	const [longitude, latitude] = JSON.parse(value).features[0].geometry.coordinates
	return { longitude, latitude }
}
</script>


================================================
FILE: frontend/src/components/Holidays.vue
================================================
<template>
	<div class="flex flex-col gap-5 w-full">
		<div class="flex flex-row justify-between items-center">
			<div class="text-lg text-gray-800 font-bold">{{ __("Upcoming Holidays") }}</div>
			<div
				v-if="holidays?.data?.length"
				id="open-holiday-list"
				class="text-sm text-gray-800 font-semibold cursor-pointer underline underline-offset-2"
			>
				{{ __("View All") }}
			</div>
		</div>

		<div class="flex flex-col bg-white rounded" v-if="upcomingHolidays?.length">
			<div
				class="flex flex-row flex-start p-4 items-center justify-between border-b"
				v-for="holiday in upcomingHolidays"
				:key="holiday.holiday_date"
			>
				<div class="flex flex-row items-center gap-3 grow">
					<FeatherIcon name="calendar" class="h-5 w-5 text-gray-500" />
					<div class="text-base font-normal text-gray-800">
						{{ __(holiday.description) }}
					</div>
				</div>
				<div class="text-base font-bold text-gray-800">
					{{ holiday.formatted_holiday_date }}
				</div>
			</div>
		</div>

		<EmptyState :message="__('You have no upcoming holidays')" v-else />
	</div>

	<ion-modal
		ref="modal"
		v-if="holidays?.data?.length"
		trigger="open-holiday-list"
		:initial-breakpoint="1"
		:breakpoints="[0, 1]"
	>
		<div class="bg-white w-full flex flex-col items-center justify-center pb-5">
			<div class="w-full pt-8 pb-5 border-b text-center">
				<span class="text-gray-900 font-bold text-lg">{{ __("Holiday List") }}</span>
			</div>
			<div class="w-full flex flex-col items-center justify-center gap-5 p-4">
				<div
					v-for="holiday in holidays.data"
					:key="holiday.holiday_date"
					class="flex flex-row items-center justify-between w-full"
				>
					<div class="flex flex-row items-center gap-3 grow">
						<FeatherIcon name="calendar" class="h-5 w-5 text-gray-500" />
						<div class="text-base font-normal text-gray-800">
							{{ __(holiday.description) }}
						</div>
					</div>
					<div
						:class="[
							'text-base font-bold',
							holiday.is_upcoming ? 'text-gray-800' : 'text-gray-500',
						]"
					>
						{{ holiday.formatted_holiday_date }}
					</div>
				</div>
			</div>
		</div>
	</ion-modal>
</template>

<script setup>
import { inject, computed } from "vue"
import { IonModal } from "@ionic/vue"
import { FeatherIcon, createResource } from "frappe-ui"

const employee = inject("$employee")
const dayjs = inject("$dayjs")
const __ = inject("$translate")

const holidays = createResource({
	url: "hrms.api.get_holidays_for_employee",
	params: {
		employee: employee.data.name,
	},
	auto: true,
	transform: (data) => {
		return data.map((holiday) => {
			const holidayDate = dayjs(holiday.holiday_date)
			holiday.is_upcoming = holidayDate.isAfter(dayjs())
			holiday.formatted_holiday_date = holidayDate.format("ddd, D MMM YYYY")
			return holiday
		})
	},
})

const upcomingHolidays = computed(() => {
	const filteredHolidays = holidays.data?.filter(
		(holiday) => holiday.is_upcoming
	)

	// show only 5 upcoming holidays
	return filteredHolidays?.slice(0, 5)
})
</script>


================================================
FILE: frontend/src/components/InstallPrompt.vue
================================================
<template>
	<!-- Install PWA dialog -->
	<Dialog v-model="showDialog">
		<template #body-title>
			<h2 class="text-lg font-bold">{{ __("Install Frappe HR") }} </h2>
		</template>
		<template #body-content>
			<p>{{ __("Get the app on your device for easy access & a better experience!") }} </p>
		</template>
		<template #actions>
			<Button variant="solid" @click="() => install()" class="py-5 w-full">
				<template #prefix><FeatherIcon name="download" class="w-4" /></template>
				{{ __("Install") }}
			</Button>
		</template>
	</Dialog>

	<!-- iOS installation info message -->
	<Popover :show="iosInstallMessage" placement="bottom">
		<template #body>
			<div
				class="mt-[calc(100vh-15rem)] flex flex-col gap-3 mx-2 rounded py-5 bg-blue-100 drop-shadow-xl"
			>
				<div
					class="flex flex-row text-center items-center justify-between mb-1 px-3"
				>
					<span class="text-base text-gray-900 font-bold">
						{{ __("Install Frappe HR") }}
					</span>
					<span class="inline-flex items-baseline">
						<FeatherIcon
							name="x"
							class="ml-auto h-4 w-4 text-gray-700"
							@click="iosInstallMessage = false"
						/>
					</span>
				</div>
				<div class="text-xs text-gray-800 px-3">
					<span class="flex flex-col gap-2">
						<span>
							{{ __("Get the app on your iPhone for easy access & a better experience") }}
						</span>
						<span class="inline-flex items-start whitespace-nowrap">
							<span>Tap&nbsp;</span>
							<FeatherIcon name="share" class="h-4 w-4 text-blue-600" />
							<span>&nbsp;and then "Add to Home Screen"</span>
						</span>
					</span>
				</div>
			</div>
		</template>
	</Popover>
</template>

<script setup>
import { ref } from "vue"

import { Dialog, Popover, FeatherIcon } from "frappe-ui"

// Initialize deferredPrompt for use later to show browser install prompt.
const deferredPrompt = ref(null)
const showDialog = ref(false)
const iosInstallMessage = ref(false)

const isIos = () => {
	// Detects if device is on iOS
	const userAgent = window.navigator.userAgent.toLowerCase()
	return /iphone|ipad|ipod/.test(userAgent)
}

// Detects if device is in standalone mode
const isInStandaloneMode = () =>
	"standalone" in window.navigator && window.navigator.standalone

// Checks if should display install popup notification:
if (isIos() && !isInStandaloneMode()) {
	iosInstallMessage.value = true
}

window.addEventListener("beforeinstallprompt", (e) => {
	// Prevent the mini-infobar from appearing on mobile
	e.preventDefault()
	// Stash the event so it can be triggered later.
	deferredPrompt.value = e
	if (isIos() && !isInStandaloneMode()) {
		iosInstallMessage.value = true
	} else {
		showDialog.value = true
	}
	// Optionally, send analytics event that PWA install promo was shown.
	console.log(`'beforeinstallprompt' event was fired.`)
})

window.addEventListener("appinstalled", () => {
	showDialog.value = false
	deferredPrompt.value = null
})

async function install() {
	deferredPrompt.value.prompt()
	showDialog.value = false
}
</script>


================================================
FILE: frontend/src/components/LeaveBalance.vue
================================================
<template>
	<div class="flex flex-col w-full">
		<div class="flex flex-row justify-between items-center px-4">
			<div class="text-lg text-gray-800 font-bold">{{ __("Leave Balance") }} </div>
			<router-link
				:to="{ name: 'LeaveApplicationListView' }"
				v-slot="{ navigate }"
				v-if="leaveBalance.data"
			>
				<div
					@click="navigate"
					class="text-sm text-gray-800 font-semibold cursor-pointer underline underline-offset-2"
				>
					{{ __("View Leave History") }}
				</div>
			</router-link>
		</div>

		<!-- Leave Balance Dashboard -->
		<div
			class="flex flex-row gap-4 overflow-x-auto py-2 mt-3"
			v-if="leaveBalance.data"
		>
			<div
				v-for="(allocation, leave_type, index) in leaveBalance.data"
				:key="leave_type"
				class="flex flex-col bg-white border-none rounded-lg drop-shadow-md gap-2 p-4 items-start first:ml-4"
			>
				<SemicircleChart
					:percentage="allocation.balance_percentage"
					:colorClass="getChartColor(index)"
				/>
				<div class="text-gray-800 font-bold text-base">
					{{ `${allocation.balance_leaves}/${allocation.allocated_leaves}` }}
				</div>
				<div class="text-gray-600 font-normal text-sm w-24 leading-4">
					{{ __("{0} balance", [__(leave_type, null, "Leave Type")]) }}
				</div>
			</div>
		</div>

		<EmptyState :message="__('You have no leaves allocated')" v-else />
	</div>
</template>

<script setup>
import SemicircleChart from "@/components/SemicircleChart.vue"
import { leaveBalance } from "@/data/leaves"
import { inject } from "vue"

const __ = inject("$translate")
const getChartColor = (index) => {
	// note: tw colors - rose-400, pink-400 & purple-500 of the old frappeui palette #918ef5
	const chartColors = ["text-[#fb7185]", "text-[#f472b6]", "text-[#918ef5]"]
	return chartColors[index % chartColors.length]
}
</script>


================================================
FILE: frontend/src/components/LeaveRequestItem.vue
================================================
<template>
	<ListItem
		:isTeamRequest="props.isTeamRequest"
		:employee="props.doc.employee"
		:employeeName="props.doc.employee_name"
	>
		<template #left>
			<LeaveIcon class="h-5 w-5 text-gray-500" />
			<div class="flex flex-col items-start gap-1.5">
				<div class="text-base font-normal text-gray-800">
					{{ __(props.doc.leave_type, null, "Leave Type") }}
				</div>
				<div class="text-xs font-normal text-gray-500">
					<span>{{ props.doc.leave_dates || getLeaveDates(props.doc) }}</span>
					<span class="whitespace-pre"> &middot; </span>
					<span class="whitespace-nowrap">{{ __("{0}d", [props.doc.total_leave_days]) }}</span>
				</div>
			</div>
		</template>
		<template #right>
			<Badge variant="outline" :theme="colorMap[status]" :label="__(status, null, 'Leave Application')" size="md" />
			<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
		</template>
	</ListItem>
</template>

<script setup>
import { computed } from "vue"
import { FeatherIcon, Badge } from "frappe-ui"

import ListItem from "@/components/ListItem.vue"
import LeaveIcon from "@/components/icons/LeaveIcon.vue"
import { getLeaveDates } from "@/data/leaves"

const props = defineProps({
	doc: {
		type: Object,
	},
	isTeamRequest: {
		type: Boolean,
		default: false,
	},
	workflowStateField: {
		type: String,
		required: false,
	},
})

const status = computed(() => {
	return props.workflowStateField ? props.doc[props.workflowStateField] : props.doc.status
})

const colorMap = {
	Approved: "green",
	Rejected: "red",
	Open: "orange",
}
</script>


================================================
FILE: frontend/src/components/Link.vue
================================================
<template>
	<Autocomplete
		ref="autocompleteRef"
		size="sm"
		v-model="value"
		:placeholder="__('Select {0}', [__(doctype)])"
		:options="options.data || []"
		:class="disabled ? 'pointer-events-none' : ''"
		:disabled="disabled"
		@update:query="handleQueryUpdate"
	/>
</template>

<script setup>
import { createResource, Autocomplete, debounce } from "frappe-ui"
import { ref, computed, watch } from "vue"

const props = defineProps({
	doctype: {
		type: String,
		required: true,
	},
	modelValue: {
		type: String,
		required: false,
		default: "",
	},
	filters: {
		type: Object,
		default: {},
	},
	disabled: {
		type: Boolean,
		default: false,
	},
})

const emit = defineEmits(["update:modelValue"])

const autocompleteRef = ref(null)
const searchText = ref("")

const value = computed({
	get: () => props.modelValue,
	set: (val) => {
		const newVal = (val && typeof val === "object" && val.value !== undefined) ? val.value : val
		emit("update:modelValue", newVal || "")
	},
})

const options = createResource({
	url: "frappe.desk.search.search_link",
	params: {
		doctype: props.doctype,
		txt: searchText.value,
		filters: props.filters,
	},
	method: "POST",
	transform: (data) => {
		return data.map((doc) => {
			const title = doc?.description?.split(",")?.[0]
			return {
				label: title ? `${title} : ${doc.value}` : doc.value,
				value: doc.value,
			}
		})
	},
})

const reloadOptions = (searchTextVal) => {
	options.update({
		params: {
			txt: searchTextVal,
			doctype: props.doctype,
			filters: props.filters
		},
	})
	options.reload()
}

const handleQueryUpdate = debounce((newQuery) => {
    const val = newQuery || ""

    if (val === "" && props.modelValue) return

    if (searchText.value === val) return
    searchText.value = val
    reloadOptions(val)
}, 300)

watch(
	() => props.doctype,
	() => {
		if (!props.doctype || props.doctype === options.doctype) return
		reloadOptions(props.modelValue)
	},
	{ immediate: true }
)
</script>


================================================
FILE: frontend/src/components/ListFiltersActionSheet.vue
================================================
<template>
	<!-- Filter Action Sheet -->
	<div
		class="bg-white w-full flex flex-col items-center justify-center pb-5 max-h-[calc(100vh-5rem)]"
	>
		<div class="w-full pt-8 pb-5 border-b text-center sticky top-0 z-[100]">
			<span class="text-gray-900 font-bold text-lg">{{ __("Filters") }} </span>
		</div>

		<div class="w-full p-4 overflow-auto">
			<div class="flex flex-col gap-5 items-center justify-center">
				<div
					v-for="filter in filterConfig"
					:key="filter.fieldname"
					class="flex flex-col w-full gap-1"
				>
					<!-- Status filter -->
					<div
						class="flex flex-col gap-1.5"
						v-if="['status', 'approval_status'].includes(filter.fieldname)"
					>
						<div class="text-gray-800 font-semibold text-base">
							{{ __(filter.label) }}
						</div>
						<div class="flex flex-row gap-2 mt-2 flex-wrap">
							<Button
								v-for="option in filter.options"
								variant="outline"
								@click="setStatusFilter(filter.fieldname, option)"
								class="text-sm text-gray-800"
								:class="[
									option === filters[filter.fieldname].value
										? '!border !border-gray-800 !text-gray-900 !bg-gray-50 !font-medium'
										: '!font-normal',
								]"
							>
								{{ __(option) }}
							</Button>
						</div>
					</div>

					<!-- Field filters -->
					<div v-else class="flex flex-col gap-2">
						<div class="text-gray-800 font-semibold text-base">
							{{ __(filter.label) }}
						</div>
						<div class="flex flex-row items-center gap-3">
							<Autocomplete
								v-if="filterConditionMap[filter.fieldtype]"
								class="mt-1 w-[75px]"
								:options="filterConditionMap[filter.fieldtype]"
								v-model="filters[filter.fieldname].condition"
							/>
							<FormField
								class="w-full"
								:fieldtype="filter.fieldtype"
								:fieldname="filter.fieldname"
								:options="filter.options"
								v-model="filters[filter.fieldname].value"
							/>
						</div>
					</div>
				</div>
			</div>
		</div>

		<!-- Filter Buttons -->
		<div
			class="flex w-full flex-row items-center justify-between gap-3 sticky bottom-0 border-t p-4 z[100]"
		>
			<Button
				@click="emit('clear-filters')"
				variant="outline"
				class="w-full py-5 text-sm"
			>
				{{ __("Clear All") }}
			</Button>
			<Button
				@click="emit('apply-filters')"
				variant="solid"
				class="w-full py-5 text-sm"
			>
				{{ __("Apply Filters") }}
			</Button>
		</div>
	</div>
</template>

<script setup>
import { computed } from "vue"
import FormField from "@/components/FormField.vue"
import { Autocomplete } from "frappe-ui"

const props = defineProps({
	filterConfig: {
		type: Array,
		required: true,
	},
	filters: {
		type: Object,
		required: true,
	},
})

const emit = defineEmits(["apply-filters", "clear-filters", "update:filters"])
const numberOperators = [
	{ label: "=", value: "=" },
	{ label: ">", value: ">" },
	{ label: "<", value: "<" },
	{ label: ">=", value: ">=" },
	{ label: "<=", value: "<=" },
]

const filterConditionMap = {
	Date: numberOperators,
	Currency: numberOperators,
}

const filters = computed({
	get() {
		return props.filters
	},
	set(value) {
		emit("update:filters", value)
	},
})

function setStatusFilter(fieldname, value) {
	if (filters.value[fieldname].value === value) {
		filters.value[fieldname].value = ""
	} else {
		filters.value[fieldname].value = value
	}
}
</script>


================================================
FILE: frontend/src/components/ListItem.vue
================================================
<template>
	<div class="flex flex-col w-full justify-center gap-2.5">
		<div class="flex flex-row items-center justify-between">
			<div class="flex flex-row items-start gap-3 grow">
				<slot name="left" />
			</div>
			<div class="flex flex-row justify-end items-center gap-2">
				<slot name="right" />
			</div>
		</div>
		<div v-if="props.isTeamRequest" class="flex flex-row items-center gap-2 pl-8">
			<EmployeeAvatar :employeeID="props.employee" />
			<div class="text-sm text-gray-600 grow">
				{{ props.employeeName }}
			</div>
		</div>
	</div>
</template>

<script setup>
import EmployeeAvatar from "@/components/EmployeeAvatar.vue"

const props = defineProps({
	isTeamRequest: {
		type: Boolean,
		default: false,
	},
	employee: {
		type: String,
		required: false,
	},
	employeeName: {
		type: String,
		required: false,
	},
})
</script>


================================================
FILE: frontend/src/components/ListView.vue
================================================
<template>
	<ion-header class="ion-no-border">
		<div class="w-full sm:w-96">
			<div
				class="flex flex-row bg-white shadow-sm py-4 px-3 items-center justify-between border-b"
			>
				<div class="flex flex-row items-center">
					<Button variant="ghost" class="!px-1 mr-1 hover:bg-white" @click="router.back()">
						<FeatherIcon name="chevron-left" class="h-5 w-5" />
					</Button>
					<h2 class="text-xl font-semibold text-gray-900">{{ pageTitle }}</h2>
				</div>

				<div class="flex flex-row gap-2">
					<Button
						id="show-filter-modal"
						icon="filter"
						variant="subtle"
						:class="[
							areFiltersApplied
								? '!border !border-gray-800 !bg-white !text-gray-900 !font-semibold'
								: '',
						]"
					/>
					<router-link
						v-if="createPermission?.data?.has_permission && props.doctype != 'Employee Checkin'"
						:to="{ name: formViewRoute }"
						v-slot="{ navigate }"
					>
						<Button variant="solid" class="mr-2" @click="navigate">
							<template #prefix>
								<FeatherIcon name="plus" class="w-4" />
							</template>
							{{ __("New", null, props.doctype) }}
						</Button>
					</router-link>
				</div>
			</div>
		</div>
	</ion-header>

	<ion-content>
		<ion-refresher slot="fixed" @ionRefresh="handleRefresh($event)">
			<ion-refresher-content></ion-refresher-content>
		</ion-refresher>

		<div
			class="flex flex-col items-center mb-7 p-4 h-full w-full sm:w-96 overflow-y-auto"
			ref="scrollContainer"
			@scroll="() => handleScroll()"
		>
			<div class="w-full">
				<TabButtons
					v-if="props.tabButtons"
					class="mt-5"
					:buttons="props.tabButtons"
					v-model="activeTab"
				/>

				<div
					class="flex flex-col bg-white rounded mt-5"
					v-if="!documents.loading && documents.data?.length"
				>
					<div
						class="p-3.5 items-center justify-between border-b cursor-pointer"
						v-for="link in documents.data"
						:key="link.name"
					>
						<component
							v-if="props.doctype === 'Employee Checkin'"
							:is="listItemComponent[doctype]"
							:doc="link"
							:isTeamRequest="isTeamRequest"
							:workflowStateField="workflowStateField"
							@click="openRequestModal(link)"
						/>
						<router-link
							v-else
							:to="{ name: detailViewRoute, params: { id: link.name } }"
							v-slot="{ navigate }"
						>
							<component
								:is="listItemComponent[doctype]"
								:doc="link"
								:isTeamRequest="isTeamRequest"
								:workflowStateField="workflowStateField"
								@click="navigate"
							/>
						</router-link>
					</div>
				</div>
				<EmptyState
					:message="__('No {0} found', [props.doctype?.toLowerCase()])"
					v-else-if="!documents.loading"
				/>

				<!-- Loading Indicator -->
				<div v-if="documents.loading" class="flex mt-2 items-center justify-center">
					<LoadingIndicator class="w-8 h-8 text-gray-800" />
				</div>
			</div>
		</div>

		<CustomIonModal trigger="show-filter-modal">
			<!-- Filter Action Sheet -->
			<template #actionSheet>
				<ListFiltersActionSheet
					:filterConfig="filterConfig"
					@applyFilters="applyFilters"
					@clearFilters="clearFilters"
					v-model:filters="filterMap"
				/>
			</template>
		</CustomIonModal>
	</ion-content>

	<ion-modal
		ref="modal"
		:is-open="isRequestModalOpen"
		@didDismiss="closeRequestModal"
		:initial-breakpoint="1"
		:breakpoints="[0, 1]"
	>
		<RequestActionSheet
			:fields="EMPLOYEE_CHECKIN_FIELDS"
			:showOpenForm="false"
			v-model="selectedRequest"
		/>
	</ion-modal>
</template>

<script setup>
import { useRouter } from "vue-router"
import { inject, ref, markRaw, watch, computed, reactive, onMounted } from "vue"
import {
	modalController,
	IonHeader,
	IonContent,
	IonModal,
	IonRefresher,
	IonRefresherContent,
} from "@ionic/vue"

import { FeatherIcon, createResource, LoadingIndicator, debounce } from "frappe-ui"

import TabButtons from "@/components/TabButtons.vue"
import EmployeeCheckinItem from "@/components/EmployeeCheckinItem.vue"
import AttendanceRequestItem from "@/components/AttendanceRequestItem.vue"
import ShiftRequestItem from "@/components/ShiftRequestItem.vue"
import ShiftAssignmentItem from "@/components/ShiftAssignmentItem.vue"
import LeaveRequestItem from "@/components/LeaveRequestItem.vue"
import ExpenseClaimItem from "@/components/ExpenseClaimItem.vue"
import EmployeeAdvanceItem from "@/components/EmployeeAdvanceItem.vue"
import ListFiltersActionSheet from "@/components/ListFiltersActionSheet.vue"
import CustomIonModal from "@/components/CustomIonModal.vue"
import RequestActionSheet from "@/components/RequestActionSheet.vue"
import { EMPLOYEE_CHECKIN_FIELDS } from "@/data/config/requestSummaryFields"

import useWorkflow from "@/composables/workflow"
import { useListUpdate } from "@/composables/realtime"

const __ = inject("$translate")
const props = defineProps({
	doctype: {
		type: String,
		required: true,
	},
	fields: {
		type: Array,
		required: true,
	},
	groupBy: {
		type: String,
		required: false,
	},
	filterConfig: {
		type: Array,
		required: true,
	},
	tabButtons: {
		type: Array,
		required: false,
	},
	pageTitle: {
		type: String,
		required: true,
	},
})

const getButtonKey = (tab) => tab?.key ?? tab

const listItemComponent = {
	"Employee Checkin": markRaw(EmployeeCheckinItem),
	"Attendance Request": markRaw(AttendanceRequestItem),
	"Shift Request": markRaw(ShiftRequestItem),
	"Shift Assignment": markRaw(ShiftAssignmentItem),
	"Leave Application": markRaw(LeaveRequestItem),
	"Expense Claim": markRaw(ExpenseClaimItem),
	"Employee Advance": markRaw(EmployeeAdvanceItem),
}

const router = useRouter()
const dayjs = inject("$dayjs")
const socket = inject("$socket")
const employee = inject("$employee")
const filterMap = reactive({})
const activeTab = ref(props.tabButtons ? getButtonKey(props.tabButtons[0]) : undefined)
const areFiltersApplied = ref(false)
const appliedFilters = ref([])
const workflowStateField = ref(null)
const isRequestModalOpen = ref(false)
const selectedRequest = ref(null)

// infinite scroll
const scrollContainer = ref(null)
const hasNextPage = ref(true)
const listOptions = ref({
	doctype: props.doctype,
	fields: props.fields,
	group_by: props.groupBy,
	order_by: `\`tab${props.doctype}\`.modified desc`,
	page_length: 50,
})

// computed properties
const isTeamRequest = computed(() => {
	return props.tabButtons && activeTab.value === getButtonKey(props.tabButtons[1])
})

const formViewRoute = computed(() => {
	return `${props.doctype.replace(/\s+/g, "")}FormView`
})

const detailViewRoute = computed(() => {
	return `${props.doctype.replace(/\s+/g, "")}DetailView`
})

const defaultFilters = computed(() => {
	const filters = []

	if (isTeamRequest.value) {
		filters.push([props.doctype, "employee", "!=", employee.data.name])
	} else {
		filters.push([props.doctype, "employee", "=", employee.data.name])
	}

	return filters
})

// resources
const documents = createResource({
	url: "frappe.desk.reportview.get",
	onSuccess: (data) => {
		if (data.values?.length < listOptions.value.page_length) {
			hasNextPage.value = false
		}
	},
	transform(data) {
		if (data.length === 0) {
			return []
		}

		// convert keys and values arrays to docs object
		const fields = data["keys"]
		const values = data["values"]
		const docs = values.map((value) => {
			const doc = {}
			fields.forEach((field, index) => {
				doc[field] = value[index]
			})
			return doc
		})

		let pagedData
		if (!documents.params.start || documents.params.start === 0) {
			pagedData = docs
		} else {
			pagedData = documents.data.concat(docs)
		}

		return pagedData
	},
})

const createPermission = createResource({
	url: "frappe.client.has_permission",
	params: { doctype: props.doctype, docname: null, perm_type: "create" },
	auto: true,
})

// helper functions
const openRequestModal = async (request) => {
	selectedRequest.value = request
	selectedRequest.value.doctype = "Employee Checkin"
	selectedRequest.value.date = request.time
	selectedRequest.value.formatted_time = dayjs(request.time).format("HH:mm a")
	selectedRequest.value.formatted_latitude = `${Number(request.latitude).toFixed(5)}°`
	selectedRequest.value.formatted_longitude = `${Number(request.longitude).toFixed(5)}°`
	isRequestModalOpen.value = true
}

const closeRequestModal = async () => {
	isRequestModalOpen.value = false
	selectedRequest.value = null
}

function initializeFilters() {
	props.filterConfig.forEach((filter) => {
		filterMap[filter.fieldname] = {
			condition: "=",
			value: null,
		}
	})

	appliedFilters.value = []
}
initializeFilters()

function prepareFilters() {
	let condition = ""
	let value = ""
	appliedFilters.value = []

	for (const fieldname in filterMap) {
		condition = filterMap[fieldname].condition
		// accessing .value because autocomplete returns an object instead of value
		if (typeof condition === "object" && condition !== null) {
			condition = condition.value
		}

		value = filterMap[fieldname].value
		if (condition && value) appliedFilters.value.push([props.doctype, fieldname, condition, value])
	}
}

function applyFilters() {
	prepareFilters()
	fetchDocumentList()
	modalController.dismiss()
	areFiltersApplied.value = appliedFilters.value.length ? true : false
}

function clearFilters() {
	initializeFilters()
	fetchDocumentList()
	modalController.dismiss()
	areFiltersApplied.value = false
}

function fetchDocumentList(start = 0) {
	if (start === 0) {
		hasNextPage.value = true
	}

	const filters = [[props.doctype, "docstatus", "!=", "2"]]
	filters.push(...defaultFilters.value)

	if (appliedFilters.value) filters.push(...appliedFilters.value)

	if (workflowStateField.value) {
		listOptions.value.fields.push(workflowStateField.value)
	}

	documents.submit({
		...listOptions.value,
		start: start || 0,
		filters: filters,
	})
}

const handleScroll = debounce(() => {
	if (!hasNextPage.value) return

	const { scrollTop, scrollHeight, clientHeight } = scrollContainer.value
	const scrollPercentage = (scrollTop / (scrollHeight - clientHeight)) * 100

	if (scrollPercentage >= 90) {
		const start = documents.params.start + listOptions.value.page_length
		fetchDocumentList(start)
	}
}, 500)

const handleRefresh = (event) => {
	setTimeout(() => {
		fetchDocumentList()
		event.target.complete()
	}, 500)
}

watch(
	() => activeTab.value,
	(_value) => {
		fetchDocumentList()
	}
)

onMounted(async () => {
	const workflow = useWorkflow(props.doctype)
	await workflow.workflowDoc.promise
	workflowStateField.value = workflow.getWorkflowStateField()
	fetchDocumentList()

	useListUpdate(socket, props.doctype, () => fetchDocumentList())
})
</script>


================================================
FILE: frontend/src/components/ProfileInfoModal.vue
================================================
<template>
	<div
		class="bg-white w-full flex flex-col items-center justify-center pb-5 max-h-[calc(100vh-5rem)]"
	>
		<!-- Header -->
		<div
			class="w-full flex flex-row gap-2 pt-8 pb-5 border-b justify-center items-center sticky top-0 z-[100]"
		>
			<span class="text-gray-900 font-bold text-lg text-center">
				{{ title }}
			</span>
		</div>

		<div class="w-full flex flex-col items-center justify-center gap-4 p-4">
			<div
				v-for="item in data"
				:key="item.fieldname"
				class="flex flex-row items-center justify-between w-full"
			>
				<div class="text-gray-600 text-base">{{ item.label }}</div>
				<FormattedField
					:value="item.value"
					:fieldtype="item.fieldtype"
					:fieldname="item.fieldname"
				/>
			</div>
		</div>
	</div>
</template>

<script setup>
import { FeatherIcon } from "frappe-ui"
import FormattedField from "@/components/FormattedField.vue"

const props = defineProps({
	title: {
		type: String,
		required: true,
	},
	data: {
		type: Array,
		required: true,
	},
})
</script>


================================================
FILE: frontend/src/components/QuickLinks.vue
================================================
<template>
	<div class="flex flex-col gap-5 my-4 w-full">
		<div class="text-lg font-medium text-gray-900">{{ title || __("Quick Links") }}</div>
		<div class="flex flex-col bg-white rounded">
			<router-link
				class="flex flex-row flex-start p-4 items-center justify-between"
				:class="link !== props.items[props.items.length - 1] && 'border-b'"
				v-for="link in props.items"
				:key="link.title"
				:to="{ name: link.route }"
			>
				<div class="flex flex-row items-center gap-3 grow">
					<component :is="link.icon" class="h-5 w-5 text-gray-500" />
					<div class="text-base font-normal text-gray-800">
						{{ link.title }}
					</div>
				</div>
				<FeatherIcon name="chevron-right" class="h-5 w-5 text-gray-500" />
			</router-link>
		</div>
	</div>
</template>

<script setup>
import { FeatherIcon } from "frappe-ui"

const props = defineProps({
	title: {
		type: String,
		required: false,
		default: "",
	},
	items: {
		type: Array,
		required: true,
	},
})
</script>


================================================
FILE: frontend/src/components/RequestActionSheet.vue
================================================
<template>
	<div
		v-if="document?.doc"
		class="bg-white w-full flex flex-col items-center justify-center pb-5 max-h-[calc(100vh-5rem)]"
	>
		<!-- Header -->
		<div
			class="w-full flex flex-row gap-2 pt-8 pb-5 border-b justify-center items-center sticky top-0 z-[100]"
		>
			<span class="text-gray-900 font-bold text-lg text-center">
				{{ __(document?.doctype) }}
			</span>
			<FeatherIcon
				v-if="props.showOpenForm"
				name="external-link"
				class="h-4 w-4 text-gray-500 cursor-pointer"
				@click="openFormView"
			/>
		</div>

		<!-- Request Summary -->
		<div class="w-full p-4 overflow-auto">
			<div class="flex flex-col items-center justify-center gap-5">
				<div
					v-for="field in fieldsWithValues"
					:key="field.fieldname"
					:class="[
						['Small Text', 'Text', 'Long Text', 'Table', 'geolocation'].includes(
							field.fieldtype
						)
							? 'flex-col'
							: 'flex-row items-center justify-between',
						'flex w-full',
					]"
				>
					<div class="text-gray-600 text-base">{{ __(field.label, null, props.modelValue?.doctype) }}</div>
					<component
						v-if="field.fieldtype === 'Table'"
						:is="field.component"
						:doc="document?.doc"
					/>
					<FormattedField
						v-else
						:value="field.value"
						:fieldtype="field.fieldtype"
						:fieldname="field.fieldname"
					/>
				</div>

				<!-- Attachments -->
				<div
					class="flex flex-col gap-2 w-full"
					v-if="attachedFiles?.data?.length"
				>
					<div class="text-gray-600 text-base">{{ __('Attachments') }}</div>
					<ul class="w-full flex flex-col items-center gap-2">
						<li
							class="bg-gray-100 rounded p-2 w-full"
							v-for="(file, index) in attachedFiles.data"
							:key="index"
						>
							<div
								class="flex flex-row items-center justify-between text-gray-700 text-sm"
							>
								<span class="grow" @click="showFilePreview(file)">
									{{ file.file_name || file.name }}
								</span>
							</div>
						</li>
					</ul>
				</div>
			</div>
		</div>

		<!-- Actions -->
		<WorkflowActionSheet
			v-if="workflow?.hasWorkflow"
			:doc="document.doc"
			:workflow="workflow"
			view="actionSheet"
		/>

		<div
			v-else-if="['Open', 'Draft'].includes(document?.doc?.[approvalField]) && hasPermission('approval')"
			class="flex w-full flex-row items-center justify-between gap-3 sticky bottom-0 border-t z-[100] p-4"
		>
			<Button
				@click="updateDocumentStatus({ status: 'Rejected' })"
				class="w-full py-5"
				variant="subtle"
				theme="red"
			>
				<template #prefix>
					<FeatherIcon name="x" class="w-4" />
				</template>
				{{ __("Reject") }}
			</Button>

			<Button
				@click="updateDocumentStatus({ status: 'Approved' })"
				class="w-full py-5"
				variant="solid"
				theme="green"
			>
				<template #prefix>
					<FeatherIcon name="check" class="w-4" />
				</template>
				{{ __("Approve") }}
			</Button>
		</div>

		<div
			v-else-if="
				document?.doc?.docstatus === 0 &&
				(document?.doc?.doctype === 'Attendance Request' ||
					['Approved', 'Rejected'].includes(document?.doc?.[approvalField])) &&
				hasPermission('submit')
			"
			class="flex w-full flex-row items-center justify-between gap-3 sticky bottom-0 border-t z-[100] p-4"
		>
			<Button
				@click="updateDocumentStatus({ docstatus: 1 })"
				class="w-full py-5"
				variant="solid"
			>
				{{ __("Submit") }}
			</Button>
		</div>

		<div
			v-else-if="document?.doc?.docstatus === 1 && hasPermission('cancel')"
			class="flex w-full flex-row items-center justify-between gap-3 sticky bottom-0 border-t z-[100] p-4"
		>
			<Button
				@click="updateDocumentStatus({ docstatus: 2 })"
				class="w-full py-5"
				variant="subtle"
				theme="red"
			>
				<template #prefix>
					<FeatherIcon name="x" class="w-4" />
				</template>
				{{ __("Cancel") }}
			</Button>
		</div>

		<!-- File Preview Modal -->
		<ion-modal
			ref="modal"
			:is-open="showPreviewModal"
			@didDismiss="showPreviewModal = false"
		>
			<FilePreviewModal :file="selectedFile" />
		</ion-modal>
	</div>
</template>

<script setup>
import { computed, inject, ref, defineAsyncComponent, onMounted } from "vue"
import { IonModal, modalController } from "@ionic/vue"
import { useRouter } from "vue-router"
import {
	toast,
	createDocumentResource,
	createResource,
	FeatherIcon,
} from "frappe-ui"

import FormattedField from "@/components/FormattedField.vue"
import FilePreviewModal from "@/components/FilePreviewModal.vue"
import WorkflowActionSheet from "@/components/WorkflowActionSheet.vue"

import { getCompanyCurrency } from "@/data/currencies"
import { formatCurrency } from "@/utils/formatters"

import useWorkflow from "@/composables/workflow"

const __ = inject("$translate")

const props = defineProps({
	fields: {
		type: Array,
		required: true,
	},
	showOpenForm: {
		type: Boolean,
		default: true,
	},
	modelValue: {
		type: Object,
		required: true,
	},
})
const router = useRouter()

let showPreviewModal = ref(false)
let selectedFile = ref({})
let workflow = ref(null)

function showFilePreview(fileObj) {
	selectedFile.value = fileObj
	showPreviewModal.value = true
}

const document = createDocumentResource({
	doctype: props.modelValue.doctype,
	name: props.modelValue.name,
	auto: true,
	onSuccess(doc) {
		attachedFiles.reload()
	},
})

const attachedFiles = createResource({
	url: "hrms.api.get_attachments",
	params: {
		dt: props.modelValue.doctype,
		dn: props.modelValue.name,
	},
})

const docPermissions = createResource({
	url: "frappe.client.get_doc_permissions",
	params: { doctype: props.modelValue.doctype, docname: props.modelValue.name },
	auto: true,
})

const permittedWriteFields = createResource({
	url: "hrms.api.get_permitted_fields_for_write",
	params: { doctype: props.modelValue.doctype },
	auto: true,
})

function hasPermission(action) {
	if (action === "approval")
		return permittedWriteFields.data?.includes(approvalField.value)
	return docPermissions.data?.permissions[action]
}

const currency = computed(() => {
	let docCurrency = document?.doc?.currency

	if (!docCurrency && document?.doc?.company) {
		docCurrency = getCompanyCurrency(document?.doc?.company)
	}
	return docCurrency
})

const fieldsWithValues = computed(() => {
	return props.fields.filter((field) => {
		if (field.fieldtype === "Currency") {
			field.value = formatCurrency(
				document.doc?.[field.fieldname],
				currency.value
			)
		} else {
			if (field.fieldtype === "Table") {
				// dynamically loading child table component as per config
				// does not work with @ alias due to vite's import analysis
				field.component = defineAsyncComponent(() =>
					import(`../components/${field.componentName}.vue`)
				)
			}
			field.value =
				document?.doc?.[field.fieldname] || props.modelValue[field.fieldname]
		}

		return field.value
	})
})

const approvalField = computed(() => {
	return props.modelValue.doctype === "Expense Claim"
		? "approval_status"
		: "status"
})

const getSuccessMessage = ({ status = "", docstatus = 0 }) => {
	if (status) {
		return __("{0} successfully!", [__(status)])
	} else if (docstatus) {
		return __("Document {0} successfully!", [
			docstatus === 1 ? __("submitted") : __("cancelled")]
		)
	}
}

const getFailureMessage = ({ status = "", docstatus = 0 }) => {
	if (status) {
		return __("{0} failed!", [status === __("Approved") ? __("Approval") : __("Rejection")])
	} else if (docstatus) {
		return __('Document {0} failed!', [docstatus === 1 ? __("submission") : __("cancellation")])
	}
}

const updateDocumentStatus = ({ status = "", docstatus = 0 }) => {
	let updateValues = {}

	if (status) updateValues[approvalField.value] = status
	if (docstatus) updateValues.docstatus = docstatus

	document.setValue.submit(
		{ ...updateValues },
		{
			onSuccess() {
				if (docstatus !== 0) modalController.dismiss()

				toast({
					title: __("Success"),
					text: getSuccessMessage({ status, docstatus }),
					icon: "check-circle",
					position: "bottom-center",
					iconClasses: "text-green-500",
				})
			},
			onError() {
				toast({
					title: __("Error"),
					text: getFailureMessage({ status, docstatus }),
					icon: "alert-circle",
					position: "bottom-center",
					iconClasses: "text-red-500",
				})
			},
		}
	)
}

const openFormView = () => {
	modalController.dismiss()
	router.push({
		name: `${props.modelValue.doctype.replace(/\s+/g, "")}DetailView`,
		params: { id: props.modelValue.name },
	})
}

onMounted(() => {
	workflow.value = useWorkflow(props.modelValue.doctype)
})
</script>

<style scoped>
ion-modal {
	--height: 100%;
}
</style>


================================================
FILE: frontend/src/components/RequestList.vue
================================================
<template>
	<div class="flex flex-col bg-white rounded mt-5 overflow-auto" v-if="props.items?.length">
		<div
			class="flex flex-row p-3.5 items-center justify-between border-b cursor-pointer"
			v-for="link in props.items"
			:key="link.name"
			@click="openRequestModal(link)"
		>
			<component
				:is="props.component || link.component"
				:doc="link"
				:workflowStateField="link.workflow_state_field"
				:isTeamRequest="props.teamRequests"
			/>
		</div>

		<router-link
			v-if="props.addListButton"
			:to="{ name: props.listButtonRoute }"
			v-slot="{ navigate }"
		>
			<Button
				variant="ghost"
				@click="navigate"
				class="w-full !text-gray-600 py-6 text-sm border-none bg-white hover:bg-white"
			>
				{{ __("View List") }}
			</Button>
		</router-link>
	</div>
	<EmptyState :message="emptyStateMessage || __('You have no requests')" v-else />

	<ion-modal
		ref="modal"
		:is-open="isRequestModalOpen"
		@didDismiss="closeRequestModal"
		:initial-breakpoint="1"
		:breakpoints="[0, 1]"
	>
		<RequestActionSheet :fields="fieldsMap[selectedRequest?.doctype]" v-model="selectedRequest" />
	</ion-modal>
</template>

<script setup>
import { ref, inject } from "vue"
import { IonModal } from "@ionic/vue"
import RequestActionSheet from "@/components/RequestActionSheet.vue"

import {
	LEAVE_FIELDS,
	EXPENSE_CLAIM_FIELDS,
	ATTENDANCE_REQUEST_FIELDS,
	SHIFT_REQUEST_FIELDS,
	SHIFT_FIELDS,
} from "@/data/config/requestSummaryFields"

const __ = inject("$translate")
const props = defineProps({
	component: {
		type: Object,
	},
	items: {
		type: Array,
	},
	teamRequests: {
		type: Boolean,
		default: false,
	},
	addListButton: {
		type: Boolean,
		default: false,
	},
	listButtonRoute: {
		type: String,
		default: "",
	},
	emptyStateMessage: {
		type: String,
		default: "",
	},
})

const fieldsMap = {
	"Leave Application": LEAVE_FIELDS,
	"Expense Claim": EXPENSE_CLAIM_FIELDS,
	"Attendance Request": ATTENDANCE_REQUEST_FIELDS,
	"Shift Request": SHIFT_REQUEST_FIELDS,
	"Shift Assignment": SHIFT_FIELDS,
}

const isRequestModalOpen = ref(false)
const selectedRequest = ref(null)

const openRequestModal = async (request) => {
	selectedRequest.value = request
	isRequestModalOpen.value = true
}

const closeRequestModal = async () => {
	isRequestModalOpen.value = false
	selectedRequest.value = null
}
</script>


================================================
FILE: frontend/src/components/RequestPanel.vue
================================================
<template>
	<div class="w-full">
		<TabButtons
			:buttons="TAB_BUTTONS"
			v-model="activeTab"
		/>
		<RequestList v-if="activeTab == 'My Requests'" :items="myRequests" />
		<RequestList
			v-else-if="activeTab == 'Team Requests'"
			:items="teamRequests"
			:teamRequests="true"
		/>
	</div>
</template>

<script setup>
import { ref, inject, onMounted, computed, markRaw } from "vue"

import TabButtons from "@/components/TabButtons.vue"
import RequestList from "@/components/RequestList.vue"

import { myAttendanceRequests, myShiftRequests, teamShiftRequests, teamAttendanceRequests } from "@/data/attendance"
import { myClaims, teamClaims } from "@/data/claims"
import { myLeaves, teamLeaves } from "@/data/leaves"

import AttendanceRequestItem from "@/components/AttendanceRequestItem.vue"
import ExpenseClaimItem from "@/components/ExpenseClaimItem.vue"
import LeaveRequestItem from "@/components/LeaveRequestItem.vue"
import ShiftRequestItem from "@/components/ShiftRequestItem.vue"

import { useListUpdate } from "@/composables/realtime"

const activeTab = ref("My Requests")
const socket = inject("$socket")

const TAB_BUTTONS = ["My Requests", "Team Requests"] // __("My Requests"), __("Team Requests")

const myRequests = computed(() =>
	updateRequestDetails(myLeaves, myClaims, myShiftRequests, myAttendanceRequests)
)

const teamRequests = computed(() =>
	updateRequestDetails(teamLeaves, teamClaims, teamShiftRequests, teamAttendanceRequests)
)

function updateRequestDetails(leaves, claims, shiftRequests, attendanceRequests) {
	const requests = [leaves, claims, shiftRequests, attendanceRequests].reduce(
		(acc, resource) => acc.concat(resource?.data || []),
		[]
	)

	const componentMap = {
		"Leave Application": LeaveRequestItem,
		"Expen
Download .txt
gitextract_bjjbcr75/

├── .editorconfig
├── .git-blame-ignore-revs
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   ├── config.yml
│   │   └── feature_request.yaml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── helper/
│   │   ├── apps.json
│   │   ├── documentation.py
│   │   ├── install.sh
│   │   ├── site_config.json
│   │   ├── translation.py
│   │   └── update_pot_file.sh
│   ├── labeler.yml
│   ├── release.yml
│   └── workflows/
│       ├── build_image.yml
│       ├── ci.yml
│       ├── docs_checker.yml
│       ├── generate-pot-file.yml
│       ├── initiate_release.yml
│       ├── labeller.yml
│       ├── linters.yml
│       ├── on_release.yml
│       ├── release_notes.yml
│       ├── run-individual-tests.yml
│       └── stale.yml
├── .gitignore
├── .gitmodules
├── .mergify.yml
├── .pre-commit-config.yaml
├── .releaserc
├── .semgrepignore
├── CODE_OF_CONDUCT.md
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── codecov.yml
├── commitlint.config.js
├── crowdin.yml
├── docker/
│   ├── docker-compose.yml
│   └── init.sh
├── frontend/
│   ├── .eslintrc.js
│   ├── .gitignore
│   ├── .prettierrc.json
│   ├── index.html
│   ├── ionic.config.json
│   ├── jsconfig.json
│   ├── package.json
│   ├── postcss.config.js
│   ├── public/
│   │   ├── frappe-push-notification.js
│   │   └── sw.js
│   ├── src/
│   │   ├── App.vue
│   │   ├── components/
│   │   │   ├── AttendanceCalendar.vue
│   │   │   ├── AttendanceRequestItem.vue
│   │   │   ├── BaseLayout.vue
│   │   │   ├── BottomTabs.vue
│   │   │   ├── CheckInPanel.vue
│   │   │   ├── CustomIonModal.vue
│   │   │   ├── EmployeeAdvanceBalance.vue
│   │   │   ├── EmployeeAdvanceItem.vue
│   │   │   ├── EmployeeAvatar.vue
│   │   │   ├── EmployeeCheckinItem.vue
│   │   │   ├── EmptyState.vue
│   │   │   ├── ExpenseAdvancesTable.vue
│   │   │   ├── ExpenseClaimItem.vue
│   │   │   ├── ExpenseClaimSummary.vue
│   │   │   ├── ExpenseItems.vue
│   │   │   ├── ExpenseTaxesTable.vue
│   │   │   ├── ExpensesTable.vue
│   │   │   ├── FilePreviewModal.vue
│   │   │   ├── FileUploaderView.vue
│   │   │   ├── FormField.vue
│   │   │   ├── FormView.vue
│   │   │   ├── FormattedField.vue
│   │   │   ├── Holidays.vue
│   │   │   ├── InstallPrompt.vue
│   │   │   ├── LeaveBalance.vue
│   │   │   ├── LeaveRequestItem.vue
│   │   │   ├── Link.vue
│   │   │   ├── ListFiltersActionSheet.vue
│   │   │   ├── ListItem.vue
│   │   │   ├── ListView.vue
│   │   │   ├── ProfileInfoModal.vue
│   │   │   ├── QuickLinks.vue
│   │   │   ├── RequestActionSheet.vue
│   │   │   ├── RequestList.vue
│   │   │   ├── RequestPanel.vue
│   │   │   ├── SalaryDetailTable.vue
│   │   │   ├── SalarySlipItem.vue
│   │   │   ├── SemicircleChart.vue
│   │   │   ├── ShiftAssignmentItem.vue
│   │   │   ├── ShiftRequestItem.vue
│   │   │   ├── TabButtons.vue
│   │   │   ├── WorkflowActionSheet.vue
│   │   │   └── icons/
│   │   │       ├── AttendanceIcon.vue
│   │   │       ├── EmployeeAdvanceIcon.vue
│   │   │       ├── ExpenseIcon.vue
│   │   │       ├── FrappeHRLogo.vue
│   │   │       ├── FrappeHRLogoType.vue
│   │   │       ├── HomeIcon.vue
│   │   │       ├── LeaveIcon.vue
│   │   │       ├── SalaryIcon.vue
│   │   │       └── ShiftIcon.vue
│   │   ├── composables/
│   │   │   ├── index.js
│   │   │   ├── realtime.js
│   │   │   └── workflow.js
│   │   ├── data/
│   │   │   ├── advances.js
│   │   │   ├── attendance.js
│   │   │   ├── claims.js
│   │   │   ├── config/
│   │   │   │   └── requestSummaryFields.js
│   │   │   ├── currencies.js
│   │   │   ├── employee.js
│   │   │   ├── employees.js
│   │   │   ├── leaves.js
│   │   │   ├── notifications.js
│   │   │   ├── session.js
│   │   │   └── user.js
│   │   ├── main.css
│   │   ├── main.js
│   │   ├── plugins/
│   │   │   └── translationsPlugin.js
│   │   ├── router/
│   │   │   ├── advances.js
│   │   │   ├── attendance.js
│   │   │   ├── claims.js
│   │   │   ├── index.js
│   │   │   ├── leaves.js
│   │   │   └── salary_slips.js
│   │   ├── socket.js
│   │   ├── theme/
│   │   │   └── variables.css
│   │   ├── utils/
│   │   │   ├── commonUtils.js
│   │   │   ├── dayjs.js
│   │   │   ├── dialogs.js
│   │   │   ├── formatters.js
│   │   │   ├── ionicConfig.js
│   │   │   └── pushNotifications.js
│   │   └── views/
│   │       ├── AppSettings.vue
│   │       ├── Home.vue
│   │       ├── InvalidEmployee.vue
│   │       ├── Login.vue
│   │       ├── Notifications.vue
│   │       ├── Profile.vue
│   │       ├── TabbedView.vue
│   │       ├── attendance/
│   │       │   ├── AttendanceRequestForm.vue
│   │       │   ├── AttendanceRequestList.vue
│   │       │   ├── Dashboard.vue
│   │       │   ├── EmployeeCheckinList.vue
│   │       │   ├── ShiftAssignmentForm.vue
│   │       │   ├── ShiftAssignmentList.vue
│   │       │   ├── ShiftRequestForm.vue
│   │       │   └── ShiftRequestList.vue
│   │       ├── employee_advance/
│   │       │   ├── Form.vue
│   │       │   └── List.vue
│   │       ├── expense_claim/
│   │       │   ├── Dashboard.vue
│   │       │   ├── Form.vue
│   │       │   └── List.vue
│   │       ├── leave/
│   │       │   ├── Dashboard.vue
│   │       │   ├── Form.vue
│   │       │   └── List.vue
│   │       └── salary_slip/
│   │           ├── Dashboard.vue
│   │           └── Detail.vue
│   ├── tailwind.config.js
│   └── vite.config.js
├── hrms/
│   ├── __init__.py
│   ├── api/
│   │   ├── __init__.py
│   │   ├── oauth.py
│   │   ├── roster.py
│   │   └── system_settings.py
│   ├── config/
│   │   ├── __init__.py
│   │   ├── desktop.py
│   │   └── docs.py
│   ├── controllers/
│   │   ├── employee_boarding_controller.py
│   │   ├── employee_reminders.py
│   │   └── tests/
│   │       └── test_employee_reminders.py
│   ├── desktop_icon/
│   │   ├── expenses.json
│   │   ├── frappe_hr.json
│   │   ├── leaves.json
│   │   ├── payroll.json
│   │   ├── people.json
│   │   ├── performance.json
│   │   ├── recruitment.json
│   │   ├── shift_&_attendance.json
│   │   ├── tax_&_benefits.json
│   │   └── tenure.json
│   ├── hooks.py
│   ├── hr/
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── dashboard_chart/
│   │   │   ├── appraisal_overview/
│   │   │   │   └── appraisal_overview.json
│   │   │   ├── attendance_count/
│   │   │   │   └── attendance_count.json
│   │   │   ├── claims_by_type/
│   │   │   │   └── claims_by_type.json
│   │   │   ├── department_wise_employee_count/
│   │   │   │   └── department_wise_employee_count.json
│   │   │   ├── department_wise_expense_claims/
│   │   │   │   └── department_wise_expense_claims.json
│   │   │   ├── department_wise_openings/
│   │   │   │   └── department_wise_openings.json
│   │   │   ├── department_wise_timesheet_hours/
│   │   │   │   └── department_wise_timesheet_hours.json
│   │   │   ├── designation_wise_employee_count/
│   │   │   │   └── designation_wise_employee_count.json
│   │   │   ├── designation_wise_openings/
│   │   │   │   └── designation_wise_openings.json
│   │   │   ├── employee_advance_status/
│   │   │   │   └── employee_advance_status.json
│   │   │   ├── employees_by_age/
│   │   │   │   └── employees_by_age.json
│   │   │   ├── employees_by_branch/
│   │   │   │   └── employees_by_branch.json
│   │   │   ├── employees_by_grade/
│   │   │   │   └── employees_by_grade.json
│   │   │   ├── employees_by_type/
│   │   │   │   └── employees_by_type.json
│   │   │   ├── expense_claims/
│   │   │   │   └── expense_claims.json
│   │   │   ├── gender_diversity_ratio/
│   │   │   │   └── gender_diversity_ratio.json
│   │   │   ├── grievance_type/
│   │   │   │   └── grievance_type.json
│   │   │   ├── hiring_vs_attrition_count/
│   │   │   │   └── hiring_vs_attrition_count.json
│   │   │   ├── interview_status/
│   │   │   │   └── interview_status.json
│   │   │   ├── job_applicant_pipeline/
│   │   │   │   └── job_applicant_pipeline.json
│   │   │   ├── job_applicant_source/
│   │   │   │   └── job_applicant_source.json
│   │   │   ├── job_applicants_by_country/
│   │   │   │   └── job_applicants_by_country.json
│   │   │   ├── job_application_frequency/
│   │   │   │   └── job_application_frequency.json
│   │   │   ├── job_application_status/
│   │   │   │   └── job_application_status.json
│   │   │   ├── job_offer_status/
│   │   │   │   └── job_offer_status.json
│   │   │   ├── shift_assignment_breakup/
│   │   │   │   └── shift_assignment_breakup.json
│   │   │   ├── timesheet_activity_breakup/
│   │   │   │   └── timesheet_activity_breakup.json
│   │   │   ├── training_type/
│   │   │   │   └── training_type.json
│   │   │   ├── y_o_y_promotions/
│   │   │   │   └── y_o_y_promotions.json
│   │   │   └── y_o_y_transfers/
│   │   │       └── y_o_y_transfers.json
│   │   ├── dashboard_chart_source/
│   │   │   ├── __init__.py
│   │   │   ├── employees_by_age/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employees_by_age.js
│   │   │   │   ├── employees_by_age.json
│   │   │   │   └── employees_by_age.py
│   │   │   └── hiring_vs_attrition_count/
│   │   │       ├── __init__.py
│   │   │       ├── hiring_vs_attrition_count.js
│   │   │       ├── hiring_vs_attrition_count.json
│   │   │       └── hiring_vs_attrition_count.py
│   │   ├── doctype/
│   │   │   ├── __init__.py
│   │   │   ├── appointment_letter/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appointment_letter.js
│   │   │   │   ├── appointment_letter.json
│   │   │   │   ├── appointment_letter.py
│   │   │   │   └── test_appointment_letter.py
│   │   │   ├── appointment_letter_content/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appointment_letter_content.json
│   │   │   │   └── appointment_letter_content.py
│   │   │   ├── appointment_letter_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appointment_letter_template.js
│   │   │   │   ├── appointment_letter_template.json
│   │   │   │   ├── appointment_letter_template.py
│   │   │   │   └── test_appointment_letter_template.py
│   │   │   ├── appraisal/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal.js
│   │   │   │   ├── appraisal.json
│   │   │   │   ├── appraisal.py
│   │   │   │   └── test_appraisal.py
│   │   │   ├── appraisal_cycle/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_cycle.js
│   │   │   │   ├── appraisal_cycle.json
│   │   │   │   ├── appraisal_cycle.py
│   │   │   │   └── test_appraisal_cycle.py
│   │   │   ├── appraisal_goal/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_goal.json
│   │   │   │   └── appraisal_goal.py
│   │   │   ├── appraisal_kra/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_kra.json
│   │   │   │   └── appraisal_kra.py
│   │   │   ├── appraisal_template/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_template.js
│   │   │   │   ├── appraisal_template.json
│   │   │   │   ├── appraisal_template.py
│   │   │   │   └── test_appraisal_template.py
│   │   │   ├── appraisal_template_goal/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_template_goal.json
│   │   │   │   └── appraisal_template_goal.py
│   │   │   ├── appraisee/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisee.json
│   │   │   │   └── appraisee.py
│   │   │   ├── attendance/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── attendance.js
│   │   │   │   ├── attendance.json
│   │   │   │   ├── attendance.py
│   │   │   │   ├── attendance_calendar.js
│   │   │   │   ├── attendance_dashboard.py
│   │   │   │   ├── attendance_list.js
│   │   │   │   └── test_attendance.py
│   │   │   ├── attendance_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── attendance_request.js
│   │   │   │   ├── attendance_request.json
│   │   │   │   ├── attendance_request.py
│   │   │   │   ├── attendance_request_dashboard.py
│   │   │   │   ├── attendance_warnings.html
│   │   │   │   └── test_attendance_request.py
│   │   │   ├── compensatory_leave_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── compensatory_leave_request.js
│   │   │   │   ├── compensatory_leave_request.json
│   │   │   │   ├── compensatory_leave_request.py
│   │   │   │   └── test_compensatory_leave_request.py
│   │   │   ├── daily_work_summary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary.js
│   │   │   │   ├── daily_work_summary.json
│   │   │   │   ├── daily_work_summary.py
│   │   │   │   ├── test_daily_work_summary.py
│   │   │   │   └── test_data/
│   │   │   │       └── test-reply.raw
│   │   │   ├── daily_work_summary_group/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary_group.js
│   │   │   │   ├── daily_work_summary_group.json
│   │   │   │   └── daily_work_summary_group.py
│   │   │   ├── daily_work_summary_group_user/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary_group_user.json
│   │   │   │   └── daily_work_summary_group_user.py
│   │   │   ├── department_approver/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── department_approver.json
│   │   │   │   └── department_approver.py
│   │   │   ├── designation_skill/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── designation_skill.json
│   │   │   │   └── designation_skill.py
│   │   │   ├── earned_leave_schedule/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── earned_leave_schedule.json
│   │   │   │   └── earned_leave_schedule.py
│   │   │   ├── employee_advance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_advance.js
│   │   │   │   ├── employee_advance.json
│   │   │   │   ├── employee_advance.py
│   │   │   │   ├── employee_advance_dashboard.py
│   │   │   │   └── test_employee_advance.py
│   │   │   ├── employee_attendance_tool/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_attendance_tool.css
│   │   │   │   ├── employee_attendance_tool.js
│   │   │   │   ├── employee_attendance_tool.json
│   │   │   │   ├── employee_attendance_tool.py
│   │   │   │   └── test_employee_attendance_tool.py
│   │   │   ├── employee_boarding_activity/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_boarding_activity.json
│   │   │   │   └── employee_boarding_activity.py
│   │   │   ├── employee_checkin/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_checkin.js
│   │   │   │   ├── employee_checkin.json
│   │   │   │   ├── employee_checkin.py
│   │   │   │   ├── employee_checkin_list.js
│   │   │   │   └── test_employee_checkin.py
│   │   │   ├── employee_feedback_criteria/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_feedback_criteria.js
│   │   │   │   ├── employee_feedback_criteria.json
│   │   │   │   ├── employee_feedback_criteria.py
│   │   │   │   └── test_employee_feedback_criteria.py
│   │   │   ├── employee_feedback_rating/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_feedback_rating.json
│   │   │   │   └── employee_feedback_rating.py
│   │   │   ├── employee_grade/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_grade.js
│   │   │   │   ├── employee_grade.json
│   │   │   │   ├── employee_grade.py
│   │   │   │   ├── employee_grade_dashboard.py
│   │   │   │   └── test_employee_grade.py
│   │   │   ├── employee_grievance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_grievance.js
│   │   │   │   ├── employee_grievance.json
│   │   │   │   ├── employee_grievance.py
│   │   │   │   ├── employee_grievance_list.js
│   │   │   │   └── test_employee_grievance.py
│   │   │   ├── employee_health_insurance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_health_insurance.js
│   │   │   │   ├── employee_health_insurance.json
│   │   │   │   ├── employee_health_insurance.py
│   │   │   │   └── test_employee_health_insurance.py
│   │   │   ├── employee_onboarding/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_onboarding.js
│   │   │   │   ├── employee_onboarding.json
│   │   │   │   ├── employee_onboarding.py
│   │   │   │   ├── employee_onboarding_list.js
│   │   │   │   └── test_employee_onboarding.py
│   │   │   ├── employee_onboarding_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_onboarding_template.js
│   │   │   │   ├── employee_onboarding_template.json
│   │   │   │   ├── employee_onboarding_template.py
│   │   │   │   ├── employee_onboarding_template_dashboard.py
│   │   │   │   └── test_employee_onboarding_template.py
│   │   │   ├── employee_performance_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_performance_feedback.js
│   │   │   │   ├── employee_performance_feedback.json
│   │   │   │   ├── employee_performance_feedback.py
│   │   │   │   └── test_employee_performance_feedback.py
│   │   │   ├── employee_promotion/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_promotion.js
│   │   │   │   ├── employee_promotion.json
│   │   │   │   ├── employee_promotion.py
│   │   │   │   └── test_employee_promotion.py
│   │   │   ├── employee_property_history/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_property_history.json
│   │   │   │   └── employee_property_history.py
│   │   │   ├── employee_referral/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_referral.js
│   │   │   │   ├── employee_referral.json
│   │   │   │   ├── employee_referral.py
│   │   │   │   ├── employee_referral_dashboard.py
│   │   │   │   ├── employee_referral_list.js
│   │   │   │   └── test_employee_referral.py
│   │   │   ├── employee_separation/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_separation.js
│   │   │   │   ├── employee_separation.json
│   │   │   │   ├── employee_separation.py
│   │   │   │   ├── employee_separation_list.js
│   │   │   │   └── test_employee_separation.py
│   │   │   ├── employee_separation_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_separation_template.js
│   │   │   │   ├── employee_separation_template.json
│   │   │   │   ├── employee_separation_template.py
│   │   │   │   ├── employee_separation_template_dashboard.py
│   │   │   │   └── test_employee_separation_template.py
│   │   │   ├── employee_skill/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_skill.json
│   │   │   │   └── employee_skill.py
│   │   │   ├── employee_skill_map/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_skill_map.js
│   │   │   │   ├── employee_skill_map.json
│   │   │   │   └── employee_skill_map.py
│   │   │   ├── employee_training/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_training.json
│   │   │   │   └── employee_training.py
│   │   │   ├── employee_transfer/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_transfer.js
│   │   │   │   ├── employee_transfer.json
│   │   │   │   ├── employee_transfer.py
│   │   │   │   └── test_employee_transfer.py
│   │   │   ├── employment_type/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employment_type.json
│   │   │   │   ├── employment_type.py
│   │   │   │   ├── test_employment_type.py
│   │   │   │   └── test_records.json
│   │   │   ├── exit_interview/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── exit_interview.js
│   │   │   │   ├── exit_interview.json
│   │   │   │   ├── exit_interview.py
│   │   │   │   ├── exit_interview_list.js
│   │   │   │   ├── exit_questionnaire_notification_template.html
│   │   │   │   └── test_exit_interview.py
│   │   │   ├── expected_skill_set/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expected_skill_set.json
│   │   │   │   └── expected_skill_set.py
│   │   │   ├── expense_claim/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim.js
│   │   │   │   ├── expense_claim.json
│   │   │   │   ├── expense_claim.py
│   │   │   │   ├── expense_claim_dashboard.py
│   │   │   │   ├── expense_claim_list.js
│   │   │   │   └── test_expense_claim.py
│   │   │   ├── expense_claim_account/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_account.json
│   │   │   │   └── expense_claim_account.py
│   │   │   ├── expense_claim_advance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_advance.json
│   │   │   │   └── expense_claim_advance.py
│   │   │   ├── expense_claim_detail/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_detail.json
│   │   │   │   └── expense_claim_detail.py
│   │   │   ├── expense_claim_type/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_claim_type.js
│   │   │   │   ├── expense_claim_type.json
│   │   │   │   ├── expense_claim_type.py
│   │   │   │   └── test_expense_claim_type.py
│   │   │   ├── expense_taxes_and_charges/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── expense_taxes_and_charges.json
│   │   │   │   └── expense_taxes_and_charges.py
│   │   │   ├── full_and_final_asset/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── full_and_final_asset.js
│   │   │   │   ├── full_and_final_asset.json
│   │   │   │   ├── full_and_final_asset.py
│   │   │   │   └── test_full_and_final_asset.py
│   │   │   ├── full_and_final_outstanding_statement/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── full_and_final_outstanding_statement.json
│   │   │   │   └── full_and_final_outstanding_statement.py
│   │   │   ├── full_and_final_statement/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── full_and_final_statement.js
│   │   │   │   ├── full_and_final_statement.json
│   │   │   │   ├── full_and_final_statement.py
│   │   │   │   ├── full_and_final_statement_list.js
│   │   │   │   ├── full_and_final_statement_loan_utils.py
│   │   │   │   └── test_full_and_final_statement.py
│   │   │   ├── goal/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── goal.js
│   │   │   │   ├── goal.json
│   │   │   │   ├── goal.py
│   │   │   │   ├── goal_list.js
│   │   │   │   ├── goal_tree.js
│   │   │   │   └── test_goal.py
│   │   │   ├── grievance_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── grievance_type.js
│   │   │   │   ├── grievance_type.json
│   │   │   │   ├── grievance_type.py
│   │   │   │   └── test_grievance_type.py
│   │   │   ├── holiday_list_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── holiday_list_assignment.js
│   │   │   │   ├── holiday_list_assignment.json
│   │   │   │   ├── holiday_list_assignment.py
│   │   │   │   └── test_holiday_list_assignment.py
│   │   │   ├── hr_settings/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── hr_settings.js
│   │   │   │   ├── hr_settings.json
│   │   │   │   ├── hr_settings.py
│   │   │   │   └── test_hr_settings.py
│   │   │   ├── identification_document_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── identification_document_type.js
│   │   │   │   ├── identification_document_type.json
│   │   │   │   ├── identification_document_type.py
│   │   │   │   └── test_identification_document_type.py
│   │   │   ├── interest/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interest.js
│   │   │   │   ├── interest.json
│   │   │   │   ├── interest.py
│   │   │   │   └── test_interest.py
│   │   │   ├── interview/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview.js
│   │   │   │   ├── interview.json
│   │   │   │   ├── interview.py
│   │   │   │   ├── interview_calendar.js
│   │   │   │   ├── interview_feedback_reminder_template.html
│   │   │   │   ├── interview_list.js
│   │   │   │   ├── interview_reminder_notification_template.html
│   │   │   │   └── test_interview.py
│   │   │   ├── interview_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_detail.json
│   │   │   │   └── interview_detail.py
│   │   │   ├── interview_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_feedback.js
│   │   │   │   ├── interview_feedback.json
│   │   │   │   ├── interview_feedback.py
│   │   │   │   └── test_interview_feedback.py
│   │   │   ├── interview_round/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_round.js
│   │   │   │   ├── interview_round.json
│   │   │   │   ├── interview_round.py
│   │   │   │   └── test_interview_round.py
│   │   │   ├── interview_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interview_type.js
│   │   │   │   ├── interview_type.json
│   │   │   │   ├── interview_type.py
│   │   │   │   └── test_interview_type.py
│   │   │   ├── interviewer/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── interviewer.json
│   │   │   │   └── interviewer.py
│   │   │   ├── job_applicant/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_applicant.js
│   │   │   │   ├── job_applicant.json
│   │   │   │   ├── job_applicant.py
│   │   │   │   ├── job_applicant_dashboard.html
│   │   │   │   ├── job_applicant_dashboard.py
│   │   │   │   ├── job_applicant_list.js
│   │   │   │   └── test_job_applicant.py
│   │   │   ├── job_applicant_source/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_applicant_source.js
│   │   │   │   ├── job_applicant_source.json
│   │   │   │   ├── job_applicant_source.py
│   │   │   │   └── test_job_applicant_source.py
│   │   │   ├── job_offer/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_offer.js
│   │   │   │   ├── job_offer.json
│   │   │   │   ├── job_offer.py
│   │   │   │   ├── job_offer_list.js
│   │   │   │   └── test_job_offer.py
│   │   │   ├── job_offer_term/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_offer_term.json
│   │   │   │   └── job_offer_term.py
│   │   │   ├── job_offer_term_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_offer_term_template.js
│   │   │   │   ├── job_offer_term_template.json
│   │   │   │   ├── job_offer_term_template.py
│   │   │   │   └── test_job_offer_term_template.py
│   │   │   ├── job_opening/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_opening.js
│   │   │   │   ├── job_opening.json
│   │   │   │   ├── job_opening.py
│   │   │   │   ├── job_opening_dashboard.py
│   │   │   │   ├── templates/
│   │   │   │   │   ├── job_opening.html
│   │   │   │   │   └── job_opening_row.html
│   │   │   │   └── test_job_opening.py
│   │   │   ├── job_opening_template/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_opening_template.js
│   │   │   │   ├── job_opening_template.json
│   │   │   │   ├── job_opening_template.py
│   │   │   │   └── test_job_opening_template.py
│   │   │   ├── job_requisition/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── job_requisition.js
│   │   │   │   ├── job_requisition.json
│   │   │   │   ├── job_requisition.py
│   │   │   │   ├── job_requisition_list.js
│   │   │   │   └── test_job_requisition.py
│   │   │   ├── kra/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── kra.js
│   │   │   │   ├── kra.json
│   │   │   │   ├── kra.py
│   │   │   │   └── test_kra.py
│   │   │   ├── leave_adjustment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_adjustment.js
│   │   │   │   ├── leave_adjustment.json
│   │   │   │   ├── leave_adjustment.py
│   │   │   │   └── test_leave_adjustment.py
│   │   │   ├── leave_allocation/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_allocation.js
│   │   │   │   ├── leave_allocation.json
│   │   │   │   ├── leave_allocation.py
│   │   │   │   ├── leave_allocation_dashboard.py
│   │   │   │   ├── leave_allocation_list.js
│   │   │   │   ├── test_earned_leave_schedule.py
│   │   │   │   ├── test_earned_leaves.py
│   │   │   │   └── test_leave_allocation.py
│   │   │   ├── leave_application/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_application.js
│   │   │   │   ├── leave_application.json
│   │   │   │   ├── leave_application.py
│   │   │   │   ├── leave_application_calendar.js
│   │   │   │   ├── leave_application_dashboard.html
│   │   │   │   ├── leave_application_dashboard.py
│   │   │   │   ├── leave_application_email_template.html
│   │   │   │   ├── leave_application_list.js
│   │   │   │   ├── test_leave_application.py
│   │   │   │   └── test_records.json
│   │   │   ├── leave_block_list/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_block_list.js
│   │   │   │   ├── leave_block_list.json
│   │   │   │   ├── leave_block_list.py
│   │   │   │   ├── leave_block_list_dashboard.py
│   │   │   │   ├── test_leave_block_list.py
│   │   │   │   └── test_records.json
│   │   │   ├── leave_block_list_allow/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_block_list_allow.json
│   │   │   │   └── leave_block_list_allow.py
│   │   │   ├── leave_block_list_date/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_block_list_date.json
│   │   │   │   └── leave_block_list_date.py
│   │   │   ├── leave_control_panel/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_control_panel.js
│   │   │   │   ├── leave_control_panel.json
│   │   │   │   ├── leave_control_panel.py
│   │   │   │   └── test_leave_control_panel.py
│   │   │   ├── leave_encashment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_encashment.js
│   │   │   │   ├── leave_encashment.json
│   │   │   │   ├── leave_encashment.py
│   │   │   │   └── test_leave_encashment.py
│   │   │   ├── leave_ledger_entry/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_ledger_entry.js
│   │   │   │   ├── leave_ledger_entry.json
│   │   │   │   ├── leave_ledger_entry.py
│   │   │   │   ├── leave_ledger_entry_list.js
│   │   │   │   └── test_leave_ledger_entry.py
│   │   │   ├── leave_period/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_period.js
│   │   │   │   ├── leave_period.json
│   │   │   │   ├── leave_period.py
│   │   │   │   ├── leave_period_dashboard.py
│   │   │   │   └── test_leave_period.py
│   │   │   ├── leave_policy/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_policy.js
│   │   │   │   ├── leave_policy.json
│   │   │   │   ├── leave_policy.py
│   │   │   │   ├── leave_policy_dashboard.py
│   │   │   │   └── test_leave_policy.py
│   │   │   ├── leave_policy_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_policy_assignment.js
│   │   │   │   ├── leave_policy_assignment.json
│   │   │   │   ├── leave_policy_assignment.py
│   │   │   │   ├── leave_policy_assignment_dashboard.py
│   │   │   │   ├── leave_policy_assignment_list.js
│   │   │   │   └── test_leave_policy_assignment.py
│   │   │   ├── leave_policy_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_policy_detail.js
│   │   │   │   ├── leave_policy_detail.json
│   │   │   │   ├── leave_policy_detail.py
│   │   │   │   └── test_leave_policy_detail.py
│   │   │   ├── leave_type/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_type.js
│   │   │   │   ├── leave_type.json
│   │   │   │   ├── leave_type.py
│   │   │   │   ├── leave_type_dashboard.py
│   │   │   │   ├── test_leave_type.py
│   │   │   │   └── test_records.json
│   │   │   ├── offer_term/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── offer_term.js
│   │   │   │   ├── offer_term.json
│   │   │   │   ├── offer_term.py
│   │   │   │   └── test_offer_term.py
│   │   │   ├── overtime_details/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_details.json
│   │   │   │   └── overtime_details.py
│   │   │   ├── overtime_salary_component/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_salary_component.json
│   │   │   │   └── overtime_salary_component.py
│   │   │   ├── overtime_slip/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_slip.js
│   │   │   │   ├── overtime_slip.json
│   │   │   │   ├── overtime_slip.py
│   │   │   │   └── test_overtime_slip.py
│   │   │   ├── overtime_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── overtime_type.js
│   │   │   │   ├── overtime_type.json
│   │   │   │   ├── overtime_type.py
│   │   │   │   └── test_overtime_type.py
│   │   │   ├── purpose_of_travel/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── purpose_of_travel.js
│   │   │   │   ├── purpose_of_travel.json
│   │   │   │   ├── purpose_of_travel.py
│   │   │   │   └── test_purpose_of_travel.py
│   │   │   ├── pwa_notification/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── pwa_notification.js
│   │   │   │   ├── pwa_notification.json
│   │   │   │   ├── pwa_notification.py
│   │   │   │   └── test_pwa_notification.py
│   │   │   ├── shift_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_assignment.js
│   │   │   │   ├── shift_assignment.json
│   │   │   │   ├── shift_assignment.py
│   │   │   │   ├── shift_assignment_calendar.js
│   │   │   │   ├── shift_assignment_list.js
│   │   │   │   └── test_shift_assignment.py
│   │   │   ├── shift_assignment_tool/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_assignment_tool.js
│   │   │   │   ├── shift_assignment_tool.json
│   │   │   │   ├── shift_assignment_tool.py
│   │   │   │   └── test_shift_assignment_tool.py
│   │   │   ├── shift_location/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_location.js
│   │   │   │   ├── shift_location.json
│   │   │   │   ├── shift_location.py
│   │   │   │   ├── shift_location_list.js
│   │   │   │   └── test_shift_location.py
│   │   │   ├── shift_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_request.js
│   │   │   │   ├── shift_request.json
│   │   │   │   ├── shift_request.py
│   │   │   │   ├── shift_request_dashboard.py
│   │   │   │   ├── shift_request_list.js
│   │   │   │   └── test_shift_request.py
│   │   │   ├── shift_schedule/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_schedule.js
│   │   │   │   ├── shift_schedule.json
│   │   │   │   ├── shift_schedule.py
│   │   │   │   └── shift_schedule_list.js
│   │   │   ├── shift_schedule_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_schedule_assignment.js
│   │   │   │   ├── shift_schedule_assignment.json
│   │   │   │   ├── shift_schedule_assignment.py
│   │   │   │   ├── shift_schedule_assignment_list.js
│   │   │   │   └── test_shift_schedule_assignment.py
│   │   │   ├── shift_type/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_type.js
│   │   │   │   ├── shift_type.json
│   │   │   │   ├── shift_type.py
│   │   │   │   ├── shift_type_dashboard.py
│   │   │   │   ├── shift_type_list.js
│   │   │   │   ├── test_records.json
│   │   │   │   └── test_shift_type.py
│   │   │   ├── skill/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── skill.js
│   │   │   │   ├── skill.json
│   │   │   │   └── skill.py
│   │   │   ├── skill_assessment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── skill_assessment.json
│   │   │   │   └── skill_assessment.py
│   │   │   ├── staffing_plan/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── staffing_plan.js
│   │   │   │   ├── staffing_plan.json
│   │   │   │   ├── staffing_plan.py
│   │   │   │   ├── staffing_plan_dashboard.py
│   │   │   │   └── test_staffing_plan.py
│   │   │   ├── staffing_plan_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── staffing_plan_detail.json
│   │   │   │   └── staffing_plan_detail.py
│   │   │   ├── training_event/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_event.py
│   │   │   │   ├── training_event.js
│   │   │   │   ├── training_event.json
│   │   │   │   ├── training_event.py
│   │   │   │   ├── training_event_calendar.js
│   │   │   │   └── training_event_dashboard.py
│   │   │   ├── training_event_employee/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── training_event_employee.json
│   │   │   │   └── training_event_employee.py
│   │   │   ├── training_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_feedback.py
│   │   │   │   ├── training_feedback.js
│   │   │   │   ├── training_feedback.json
│   │   │   │   └── training_feedback.py
│   │   │   ├── training_program/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_program.py
│   │   │   │   ├── training_program.js
│   │   │   │   ├── training_program.json
│   │   │   │   ├── training_program.py
│   │   │   │   └── training_program_dashboard.py
│   │   │   ├── training_result/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_training_result.py
│   │   │   │   ├── training_result.js
│   │   │   │   ├── training_result.json
│   │   │   │   └── training_result.py
│   │   │   ├── training_result_employee/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── training_result_employee.json
│   │   │   │   └── training_result_employee.py
│   │   │   ├── travel_itinerary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── travel_itinerary.json
│   │   │   │   └── travel_itinerary.py
│   │   │   ├── travel_request/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_travel_request.py
│   │   │   │   ├── travel_request.js
│   │   │   │   ├── travel_request.json
│   │   │   │   └── travel_request.py
│   │   │   ├── travel_request_costing/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── travel_request_costing.json
│   │   │   │   └── travel_request_costing.py
│   │   │   ├── upload_attendance/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_upload_attendance.py
│   │   │   │   ├── upload_attendance.js
│   │   │   │   ├── upload_attendance.json
│   │   │   │   └── upload_attendance.py
│   │   │   ├── vehicle_log/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── test_vehicle_log.py
│   │   │   │   ├── vehicle_log.js
│   │   │   │   ├── vehicle_log.json
│   │   │   │   └── vehicle_log.py
│   │   │   ├── vehicle_service/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── vehicle_service.json
│   │   │   │   └── vehicle_service.py
│   │   │   └── vehicle_service_item/
│   │   │       ├── __init__.py
│   │   │       ├── test_vehicle_service_item.py
│   │   │       ├── vehicle_service_item.js
│   │   │       ├── vehicle_service_item.json
│   │   │       └── vehicle_service_item.py
│   │   ├── employee_property_update.js
│   │   ├── hr_dashboard/
│   │   │   ├── attendance/
│   │   │   │   └── attendance.json
│   │   │   ├── employee_lifecycle/
│   │   │   │   └── employee_lifecycle.json
│   │   │   ├── expense_claims/
│   │   │   │   └── expense_claims.json
│   │   │   ├── human_resource/
│   │   │   │   └── human_resource.json
│   │   │   └── recruitment/
│   │   │       └── recruitment.json
│   │   ├── notification/
│   │   │   ├── __init__.py
│   │   │   ├── exit_interview_scheduled/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── exit_interview_scheduled.json
│   │   │   │   ├── exit_interview_scheduled.md
│   │   │   │   └── exit_interview_scheduled.py
│   │   │   ├── training_feedback/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── training_feedback.html
│   │   │   │   ├── training_feedback.json
│   │   │   │   ├── training_feedback.md
│   │   │   │   └── training_feedback.py
│   │   │   └── training_scheduled/
│   │   │       ├── __init__.py
│   │   │       ├── training_scheduled.html
│   │   │       ├── training_scheduled.json
│   │   │       ├── training_scheduled.md
│   │   │       └── training_scheduled.py
│   │   ├── number_card/
│   │   │   ├── accepted_job_applicants/
│   │   │   │   └── accepted_job_applicants.json
│   │   │   ├── applicant_to_hire_percentage/
│   │   │   │   └── applicant_to_hire_percentage.json
│   │   │   ├── approved_claims_(this_month)/
│   │   │   │   └── approved_claims_(this_month).json
│   │   │   ├── early_exit_(this_month)/
│   │   │   │   └── early_exit_(this_month).json
│   │   │   ├── employee_exits_(this_year)/
│   │   │   │   └── employee_exits_(this_year).json
│   │   │   ├── employees_joining_(this_quarter)/
│   │   │   │   └── employees_joining_(this_quarter).json
│   │   │   ├── employees_relieving_(this_quarter)/
│   │   │   │   └── employees_relieving_(this_quarter).json
│   │   │   ├── expense_claims_(this_month)/
│   │   │   │   └── expense_claims_(this_month).json
│   │   │   ├── holidays_in_this_month/
│   │   │   │   └── holidays_in_this_month.json
│   │   │   ├── job_offer_acceptance_rate/
│   │   │   │   └── job_offer_acceptance_rate.json
│   │   │   ├── job_offers_(this_month)/
│   │   │   │   └── job_offers_(this_month).json
│   │   │   ├── job_openings/
│   │   │   │   └── job_openings.json
│   │   │   ├── late_entry_(this_month)/
│   │   │   │   └── late_entry_(this_month).json
│   │   │   ├── new_hires_(this_year)/
│   │   │   │   └── new_hires_(this_year).json
│   │   │   ├── number_of_employees_on_leave_(this_month)/
│   │   │   │   └── number_of_employees_on_leave_(this_month).json
│   │   │   ├── number_of_employees_on_leave_(today)/
│   │   │   │   └── number_of_employees_on_leave_(today).json
│   │   │   ├── onboardings_(this_month)/
│   │   │   │   └── onboardings_(this_month).json
│   │   │   ├── promotions_(this_month)/
│   │   │   │   └── promotions_(this_month).json
│   │   │   ├── rejected_claims_(this_month)/
│   │   │   │   └── rejected_claims_(this_month).json
│   │   │   ├── rejected_job_applicants/
│   │   │   │   └── rejected_job_applicants.json
│   │   │   ├── separations_(this_month)/
│   │   │   │   └── separations_(this_month).json
│   │   │   ├── time_to_fill/
│   │   │   │   └── time_to_fill.json
│   │   │   ├── total_absent_(this_month)/
│   │   │   │   └── total_absent_(this_month).json
│   │   │   ├── total_applicants_(this_month)/
│   │   │   │   └── total_applicants_(this_month).json
│   │   │   ├── total_employees/
│   │   │   │   └── total_employees.json
│   │   │   ├── total_present_(this_month)/
│   │   │   │   └── total_present_(this_month).json
│   │   │   ├── trainings_(this_month)/
│   │   │   │   └── trainings_(this_month).json
│   │   │   └── transfers_(this_month)/
│   │   │       └── transfers_(this_month).json
│   │   ├── page/
│   │   │   ├── __init__.py
│   │   │   ├── organizational_chart/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── organizational_chart.js
│   │   │   │   ├── organizational_chart.json
│   │   │   │   ├── organizational_chart.py
│   │   │   │   └── test_organizational_chart.py
│   │   │   └── team_updates/
│   │   │       ├── __init__.py
│   │   │       ├── team_update_row.html
│   │   │       ├── team_updates.css
│   │   │       ├── team_updates.js
│   │   │       ├── team_updates.json
│   │   │       └── team_updates.py
│   │   ├── print_format/
│   │   │   ├── __init__.py
│   │   │   ├── job_offer/
│   │   │   │   ├── __init__.py
│   │   │   │   └── job_offer.json
│   │   │   └── standard_appointment_letter/
│   │   │       ├── __init__.py
│   │   │       ├── standard_appointment_letter.html
│   │   │       └── standard_appointment_letter.json
│   │   ├── report/
│   │   │   ├── __init__.py
│   │   │   ├── appraisal_overview/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── appraisal_overview.js
│   │   │   │   ├── appraisal_overview.json
│   │   │   │   ├── appraisal_overview.py
│   │   │   │   └── test_appraisal_overview.py
│   │   │   ├── daily_work_summary_replies/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── daily_work_summary_replies.js
│   │   │   │   ├── daily_work_summary_replies.json
│   │   │   │   └── daily_work_summary_replies.py
│   │   │   ├── employee_advance_summary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_advance_summary.js
│   │   │   │   ├── employee_advance_summary.json
│   │   │   │   └── employee_advance_summary.py
│   │   │   ├── employee_analytics/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_analytics.js
│   │   │   │   ├── employee_analytics.json
│   │   │   │   ├── employee_analytics.py
│   │   │   │   └── test_employee_analytics.py
│   │   │   ├── employee_birthday/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_birthday.js
│   │   │   │   ├── employee_birthday.json
│   │   │   │   ├── employee_birthday.py
│   │   │   │   └── test_employee_birthday.py
│   │   │   ├── employee_exits/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_exits.js
│   │   │   │   ├── employee_exits.json
│   │   │   │   ├── employee_exits.py
│   │   │   │   └── test_employee_exits.py
│   │   │   ├── employee_hours_utilization_based_on_timesheet/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_hours_utilization_based_on_timesheet.js
│   │   │   │   ├── employee_hours_utilization_based_on_timesheet.json
│   │   │   │   ├── employee_hours_utilization_based_on_timesheet.py
│   │   │   │   └── test_employee_util.py
│   │   │   ├── employee_information/
│   │   │   │   ├── __init__.py
│   │   │   │   └── employee_information.json
│   │   │   ├── employee_leave_balance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_leave_balance.js
│   │   │   │   ├── employee_leave_balance.json
│   │   │   │   ├── employee_leave_balance.py
│   │   │   │   └── test_employee_leave_balance.py
│   │   │   ├── employee_leave_balance_summary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_leave_balance_summary.js
│   │   │   │   ├── employee_leave_balance_summary.json
│   │   │   │   ├── employee_leave_balance_summary.py
│   │   │   │   └── test_employee_leave_balance_summary.py
│   │   │   ├── employees_working_on_a_holiday/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employees_working_on_a_holiday.js
│   │   │   │   ├── employees_working_on_a_holiday.json
│   │   │   │   ├── employees_working_on_a_holiday.py
│   │   │   │   └── test_employees_working_on_a_holiday.py
│   │   │   ├── leave_ledger/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── leave_ledger.js
│   │   │   │   ├── leave_ledger.json
│   │   │   │   ├── leave_ledger.py
│   │   │   │   └── test_leave_ledger.py
│   │   │   ├── monthly_attendance_sheet/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── monthly_attendance_sheet.js
│   │   │   │   ├── monthly_attendance_sheet.json
│   │   │   │   ├── monthly_attendance_sheet.py
│   │   │   │   └── test_monthly_attendance_sheet.py
│   │   │   ├── project_profitability/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── project_profitability.js
│   │   │   │   ├── project_profitability.json
│   │   │   │   ├── project_profitability.py
│   │   │   │   └── test_project_profitability.py
│   │   │   ├── recruitment_analytics/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── recruitment_analytics.js
│   │   │   │   ├── recruitment_analytics.json
│   │   │   │   └── recruitment_analytics.py
│   │   │   ├── shift_attendance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── shift_attendance.js
│   │   │   │   ├── shift_attendance.json
│   │   │   │   ├── shift_attendance.py
│   │   │   │   └── test_shift_attendance.py
│   │   │   ├── unpaid_expense_claim/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── unpaid_expense_claim.js
│   │   │   │   ├── unpaid_expense_claim.json
│   │   │   │   └── unpaid_expense_claim.py
│   │   │   └── vehicle_expenses/
│   │   │       ├── __init__.py
│   │   │       ├── test_vehicle_expenses.py
│   │   │       ├── vehicle_expenses.js
│   │   │       ├── vehicle_expenses.json
│   │   │       └── vehicle_expenses.py
│   │   ├── utils.py
│   │   ├── web_form/
│   │   │   ├── __init__.py
│   │   │   └── job_application/
│   │   │       ├── __init__.py
│   │   │       ├── job_application.js
│   │   │       ├── job_application.json
│   │   │       └── job_application.py
│   │   └── workspace/
│   │       ├── expenses/
│   │       │   └── expenses.json
│   │       ├── leaves/
│   │       │   └── leaves.json
│   │       ├── people/
│   │       │   └── people.json
│   │       ├── performance/
│   │       │   └── performance.json
│   │       ├── recruitment/
│   │       │   └── recruitment.json
│   │       ├── shift_&_attendance/
│   │       │   └── shift_&_attendance.json
│   │       └── tenure/
│   │           └── tenure.json
│   ├── install.py
│   ├── locale/
│   │   ├── af.po
│   │   ├── ar.po
│   │   ├── bs.po
│   │   ├── cs.po
│   │   ├── da.po
│   │   ├── de.po
│   │   ├── eo.po
│   │   ├── es.po
│   │   ├── fa.po
│   │   ├── fi.po
│   │   ├── fr.po
│   │   ├── hr.po
│   │   ├── hu.po
│   │   ├── id.po
│   │   ├── it.po
│   │   ├── main.pot
│   │   ├── my.po
│   │   ├── nb.po
│   │   ├── nl.po
│   │   ├── pl.po
│   │   ├── pt.po
│   │   ├── pt_BR.po
│   │   ├── ru.po
│   │   ├── sl.po
│   │   ├── sr.po
│   │   ├── sr_CS.po
│   │   ├── sv.po
│   │   ├── ta.po
│   │   ├── th.po
│   │   ├── tr.po
│   │   ├── vi.po
│   │   ├── zh.po
│   │   └── zh_TW.po
│   ├── mixins/
│   │   ├── appraisal.py
│   │   └── pwa_notifications.py
│   ├── modules.txt
│   ├── overrides/
│   │   ├── company.py
│   │   ├── dashboard_overrides.py
│   │   ├── employee_master.py
│   │   ├── employee_payment_entry.py
│   │   ├── employee_project.py
│   │   └── employee_timesheet.py
│   ├── patches/
│   │   ├── post_install/
│   │   │   ├── create_country_fixtures.py
│   │   │   ├── delete_employee_transfer_property_doctype.py
│   │   │   ├── move_doctype_reports_and_notification_from_hr_to_payroll.py
│   │   │   ├── move_payroll_setting_separately_from_hr_settings.py
│   │   │   ├── move_tax_slabs_from_payroll_period_to_income_tax_slab.py
│   │   │   ├── rename_stop_to_send_birthday_reminders.py
│   │   │   ├── set_company_in_leave_ledger_entry.py
│   │   │   ├── set_department_for_doctypes.py
│   │   │   ├── set_payroll_cost_centers.py
│   │   │   ├── set_payroll_entry_status.py
│   │   │   ├── set_training_event_attendance.py
│   │   │   ├── update_allocate_on_in_leave_type.py
│   │   │   ├── update_employee_advance_status.py
│   │   │   ├── update_expense_claim_status_for_paid_advances.py
│   │   │   ├── update_performance_module_changes.py
│   │   │   ├── update_reason_for_resignation_in_employee.py
│   │   │   ├── update_start_end_date_for_old_shift_assignment.py
│   │   │   └── updates_for_multi_currency_payroll.py
│   │   ├── v14_0/
│   │   │   ├── add_expense_claim_to_repost_settings.py
│   │   │   ├── create_custom_field_for_appraisal_template.py
│   │   │   ├── create_custom_field_in_loan.py
│   │   │   ├── create_marginal_relief_field_for_india_localisation.py
│   │   │   ├── create_vehicle_service_item.py
│   │   │   ├── update_ess_user_access.py
│   │   │   ├── update_loan_repayment_repay_from_salary.py
│   │   │   ├── update_payroll_frequency_to_none_if_salary_slip_is_based_on_timesheet.py
│   │   │   ├── update_repay_from_salary_and_payroll_payable_account_fields.py
│   │   │   └── update_title_in_employee_onboarding_and_separation_templates.py
│   │   ├── v15_0/
│   │   │   ├── add_leave_type_permission_for_ess.py
│   │   │   ├── add_loan_docperms_to_ess.py
│   │   │   ├── call_set_total_advance_paid_on_advance_documents.py
│   │   │   ├── check_version_compatibility_with_frappe.py
│   │   │   ├── create_accounting_dimensions_in_leave_encashment.py
│   │   │   ├── create_marginal_relief_field_for_india_localisation.py
│   │   │   ├── enable_allow_checkin_setting.py
│   │   │   ├── fix_timesheet_status.py
│   │   │   ├── make_hr_settings_tab_in_company_master.py
│   │   │   ├── migrate_loan_type_to_loan_product.py
│   │   │   ├── migrate_shift_assignment_schedule_to_shift_schedule.py
│   │   │   ├── notify_about_loan_app_separation.py
│   │   │   ├── rename_and_update_leave_encashment_fields.py
│   │   │   ├── rename_claim_date_to_payroll_date_in_employee_benefit_claim.py
│   │   │   ├── rename_enable_late_entry_early_exit_grace_period.py
│   │   │   ├── set_default_asset_action_in_fnf.py
│   │   │   ├── set_half_day_status_to_present_in_exisiting_half_day_attendance.py
│   │   │   ├── update_advance_payment_ledger_amount.py
│   │   │   └── update_payment_status_for_leave_encashment.py
│   │   ├── v16_0/
│   │   │   ├── create_custom_field_for_employee_advance_in_employee_master.py
│   │   │   ├── create_holiday_list_assignments.py
│   │   │   ├── delete_old_workspaces.py
│   │   │   ├── set_base_paid_amount_in_employee_advance.py
│   │   │   └── set_currency_and_base_fields_in_expense_claim.py
│   │   └── v1_0/
│   │       └── rearrange_employee_fields.py
│   ├── patches.txt
│   ├── payroll/
│   │   ├── __init__.py
│   │   ├── dashboard_chart/
│   │   │   ├── department_wise_salary(last_month)/
│   │   │   │   └── department_wise_salary(last_month).json
│   │   │   ├── designation_wise_salary(last_month)/
│   │   │   │   └── designation_wise_salary(last_month).json
│   │   │   └── outgoing_salary/
│   │   │       └── outgoing_salary.json
│   │   ├── data/
│   │   │   └── salary_components.json
│   │   ├── doctype/
│   │   │   ├── __init__.py
│   │   │   ├── additional_salary/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── additional_salary.js
│   │   │   │   ├── additional_salary.json
│   │   │   │   ├── additional_salary.py
│   │   │   │   └── test_additional_salary.py
│   │   │   ├── arrear/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── arrear.js
│   │   │   │   ├── arrear.json
│   │   │   │   ├── arrear.py
│   │   │   │   └── test_arrear.py
│   │   │   ├── bulk_salary_structure_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── bulk_salary_structure_assignment.js
│   │   │   │   ├── bulk_salary_structure_assignment.json
│   │   │   │   ├── bulk_salary_structure_assignment.py
│   │   │   │   └── test_bulk_salary_structure_assignment.py
│   │   │   ├── employee_benefit_application/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_application.js
│   │   │   │   ├── employee_benefit_application.json
│   │   │   │   ├── employee_benefit_application.py
│   │   │   │   └── test_employee_benefit_application.py
│   │   │   ├── employee_benefit_application_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_application_detail.json
│   │   │   │   └── employee_benefit_application_detail.py
│   │   │   ├── employee_benefit_claim/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_claim.js
│   │   │   │   ├── employee_benefit_claim.json
│   │   │   │   ├── employee_benefit_claim.py
│   │   │   │   └── test_employee_benefit_claim.py
│   │   │   ├── employee_benefit_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_detail.json
│   │   │   │   └── employee_benefit_detail.py
│   │   │   ├── employee_benefit_ledger/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_benefit_ledger.js
│   │   │   │   ├── employee_benefit_ledger.json
│   │   │   │   ├── employee_benefit_ledger.py
│   │   │   │   └── employee_benefit_ledger_list.js
│   │   │   ├── employee_cost_center/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_cost_center.json
│   │   │   │   └── employee_cost_center.py
│   │   │   ├── employee_incentive/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_incentive.js
│   │   │   │   ├── employee_incentive.json
│   │   │   │   ├── employee_incentive.py
│   │   │   │   └── test_employee_incentive.py
│   │   │   ├── employee_other_income/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_other_income.js
│   │   │   │   ├── employee_other_income.json
│   │   │   │   ├── employee_other_income.py
│   │   │   │   └── test_employee_other_income.py
│   │   │   ├── employee_tax_exemption_category/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_category.js
│   │   │   │   ├── employee_tax_exemption_category.json
│   │   │   │   ├── employee_tax_exemption_category.py
│   │   │   │   └── test_employee_tax_exemption_category.py
│   │   │   ├── employee_tax_exemption_declaration/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_declaration.js
│   │   │   │   ├── employee_tax_exemption_declaration.json
│   │   │   │   ├── employee_tax_exemption_declaration.py
│   │   │   │   └── test_employee_tax_exemption_declaration.py
│   │   │   ├── employee_tax_exemption_declaration_category/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_declaration_category.json
│   │   │   │   └── employee_tax_exemption_declaration_category.py
│   │   │   ├── employee_tax_exemption_proof_submission/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_proof_submission.js
│   │   │   │   ├── employee_tax_exemption_proof_submission.json
│   │   │   │   ├── employee_tax_exemption_proof_submission.py
│   │   │   │   └── test_employee_tax_exemption_proof_submission.py
│   │   │   ├── employee_tax_exemption_proof_submission_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_proof_submission_detail.json
│   │   │   │   └── employee_tax_exemption_proof_submission_detail.py
│   │   │   ├── employee_tax_exemption_sub_category/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── employee_tax_exemption_sub_category.js
│   │   │   │   ├── employee_tax_exemption_sub_category.json
│   │   │   │   ├── employee_tax_exemption_sub_category.py
│   │   │   │   └── test_employee_tax_exemption_sub_category.py
│   │   │   ├── gratuity/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity.js
│   │   │   │   ├── gratuity.json
│   │   │   │   ├── gratuity.py
│   │   │   │   ├── gratuity_dashboard.py
│   │   │   │   ├── gratuity_list.js
│   │   │   │   └── test_gratuity.py
│   │   │   ├── gratuity_applicable_component/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity_applicable_component.json
│   │   │   │   └── gratuity_applicable_component.py
│   │   │   ├── gratuity_rule/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity_rule.js
│   │   │   │   ├── gratuity_rule.json
│   │   │   │   ├── gratuity_rule.py
│   │   │   │   ├── gratuity_rule_dashboard.py
│   │   │   │   └── test_gratuity_rule.py
│   │   │   ├── gratuity_rule_slab/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── gratuity_rule_slab.json
│   │   │   │   └── gratuity_rule_slab.py
│   │   │   ├── income_tax_slab/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_slab.js
│   │   │   │   ├── income_tax_slab.json
│   │   │   │   ├── income_tax_slab.py
│   │   │   │   └── test_income_tax_slab.py
│   │   │   ├── income_tax_slab_other_charges/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_slab_other_charges.json
│   │   │   │   └── income_tax_slab_other_charges.py
│   │   │   ├── payroll_correction/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_correction.js
│   │   │   │   ├── payroll_correction.json
│   │   │   │   ├── payroll_correction.py
│   │   │   │   └── test_payroll_correction.py
│   │   │   ├── payroll_correction_child/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_correction_child.json
│   │   │   │   └── payroll_correction_child.py
│   │   │   ├── payroll_employee_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_employee_detail.json
│   │   │   │   └── payroll_employee_detail.py
│   │   │   ├── payroll_entry/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_entry.js
│   │   │   │   ├── payroll_entry.json
│   │   │   │   ├── payroll_entry.py
│   │   │   │   ├── payroll_entry_dashboard.py
│   │   │   │   ├── payroll_entry_list.js
│   │   │   │   └── test_payroll_entry.py
│   │   │   ├── payroll_period/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_period.js
│   │   │   │   ├── payroll_period.json
│   │   │   │   ├── payroll_period.py
│   │   │   │   ├── payroll_period_dashboard.py
│   │   │   │   └── test_payroll_period.py
│   │   │   ├── payroll_period_date/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_period_date.json
│   │   │   │   └── payroll_period_date.py
│   │   │   ├── payroll_settings/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── payroll_settings.js
│   │   │   │   ├── payroll_settings.json
│   │   │   │   ├── payroll_settings.py
│   │   │   │   └── test_payroll_settings.py
│   │   │   ├── retention_bonus/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── retention_bonus.js
│   │   │   │   ├── retention_bonus.json
│   │   │   │   ├── retention_bonus.py
│   │   │   │   └── test_retention_bonus.py
│   │   │   ├── salary_component/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_component.js
│   │   │   │   ├── salary_component.json
│   │   │   │   ├── salary_component.py
│   │   │   │   ├── test_records.json
│   │   │   │   └── test_salary_component.py
│   │   │   ├── salary_component_account/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_component_account.json
│   │   │   │   └── salary_component_account.py
│   │   │   ├── salary_detail/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_detail.json
│   │   │   │   └── salary_detail.py
│   │   │   ├── salary_slip/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip.js
│   │   │   │   ├── salary_slip.json
│   │   │   │   ├── salary_slip.py
│   │   │   │   ├── salary_slip_list.js
│   │   │   │   ├── salary_slip_loan_utils.py
│   │   │   │   └── test_salary_slip.py
│   │   │   ├── salary_slip_leave/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip_leave.json
│   │   │   │   └── salary_slip_leave.py
│   │   │   ├── salary_slip_loan/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip_loan.json
│   │   │   │   └── salary_slip_loan.py
│   │   │   ├── salary_slip_timesheet/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_slip_timesheet.json
│   │   │   │   └── salary_slip_timesheet.py
│   │   │   ├── salary_structure/
│   │   │   │   ├── README.md
│   │   │   │   ├── __init__.py
│   │   │   │   ├── condition_and_formula_help.html
│   │   │   │   ├── salary_structure.js
│   │   │   │   ├── salary_structure.json
│   │   │   │   ├── salary_structure.py
│   │   │   │   ├── salary_structure_dashboard.py
│   │   │   │   ├── salary_structure_list.js
│   │   │   │   └── test_salary_structure.py
│   │   │   ├── salary_structure_assignment/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_structure_assignment.js
│   │   │   │   ├── salary_structure_assignment.json
│   │   │   │   ├── salary_structure_assignment.py
│   │   │   │   └── test_salary_structure_assignment.py
│   │   │   ├── salary_withholding/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_withholding.js
│   │   │   │   ├── salary_withholding.json
│   │   │   │   ├── salary_withholding.py
│   │   │   │   └── test_salary_withholding.py
│   │   │   ├── salary_withholding_cycle/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_withholding_cycle.json
│   │   │   │   └── salary_withholding_cycle.py
│   │   │   └── taxable_salary_slab/
│   │   │       ├── __init__.py
│   │   │       ├── taxable_salary_slab.json
│   │   │       └── taxable_salary_slab.py
│   │   ├── notification/
│   │   │   ├── as
│   │   │   └── retention_bonus/
│   │   │       ├── __init__.py
│   │   │       ├── retention_bonus.json
│   │   │       ├── retention_bonus.md
│   │   │       └── retention_bonus.py
│   │   ├── number_card/
│   │   │   ├── total_declaration_submitted/
│   │   │   │   └── total_declaration_submitted.json
│   │   │   ├── total_incentive_given(last_month)/
│   │   │   │   └── total_incentive_given(last_month).json
│   │   │   ├── total_outgoing_salary(last_month)/
│   │   │   │   └── total_outgoing_salary(last_month).json
│   │   │   └── total_salary_structure/
│   │   │       └── total_salary_structure.json
│   │   ├── payroll_dashboard/
│   │   │   └── payroll/
│   │   │       └── payroll.json
│   │   ├── print_format/
│   │   │   ├── __init__.py
│   │   │   ├── salary_slip_based_on_timesheet/
│   │   │   │   ├── __init__.py
│   │   │   │   └── salary_slip_based_on_timesheet.json
│   │   │   ├── salary_slip_standard/
│   │   │   │   ├── __init__.py
│   │   │   │   └── salary_slip_standard.json
│   │   │   └── salary_slip_with_year_to_date/
│   │   │       ├── __init__.py
│   │   │       └── salary_slip_with_year_to_date.json
│   │   ├── report/
│   │   │   ├── __init__.py
│   │   │   ├── accrued_earnings_report/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── accrued_earnings_report.js
│   │   │   │   ├── accrued_earnings_report.json
│   │   │   │   └── accrued_earnings_report.py
│   │   │   ├── bank_remittance/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── bank_remittance.js
│   │   │   │   ├── bank_remittance.json
│   │   │   │   └── bank_remittance.py
│   │   │   ├── income_tax_computation/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_computation.js
│   │   │   │   ├── income_tax_computation.json
│   │   │   │   ├── income_tax_computation.py
│   │   │   │   └── test_income_tax_computation.py
│   │   │   ├── income_tax_deductions/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── income_tax_deductions.js
│   │   │   │   ├── income_tax_deductions.json
│   │   │   │   ├── income_tax_deductions.py
│   │   │   │   └── test_income_tax_deductions.py
│   │   │   ├── professional_tax_deductions/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── professional_tax_deductions.js
│   │   │   │   ├── professional_tax_deductions.json
│   │   │   │   └── professional_tax_deductions.py
│   │   │   ├── provident_fund_deductions/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── provident_fund_deductions.js
│   │   │   │   ├── provident_fund_deductions.json
│   │   │   │   └── provident_fund_deductions.py
│   │   │   ├── salary_payments_based_on_payment_mode/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_payments_based_on_payment_mode.js
│   │   │   │   ├── salary_payments_based_on_payment_mode.json
│   │   │   │   └── salary_payments_based_on_payment_mode.py
│   │   │   ├── salary_payments_via_ecs/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── salary_payments_via_ecs.js
│   │   │   │   ├── salary_payments_via_ecs.json
│   │   │   │   └── salary_payments_via_ecs.py
│   │   │   └── salary_register/
│   │   │       ├── __init__.py
│   │   │       ├── salary_register.html
│   │   │       ├── salary_register.js
│   │   │       ├── salary_register.json
│   │   │       └── salary_register.py
│   │   ├── utils.py
│   │   └── workspace/
│   │       ├── payroll/
│   │       │   └── payroll.json
│   │       └── tax_&_benefits/
│   │           └── tax_&_benefits.json
│   ├── public/
│   │   ├── .gitkeep
│   │   ├── build.json
│   │   ├── js/
│   │   │   ├── erpnext/
│   │   │   │   ├── bank_transaction.js
│   │   │   │   ├── company.js
│   │   │   │   ├── delivery_trip.js
│   │   │   │   ├── department.js
│   │   │   │   ├── employee.js
│   │   │   │   ├── journal_entry.js
│   │   │   │   ├── payment_entry.js
│   │   │   │   └── timesheet.js
│   │   │   ├── hierarchy-chart.bundle.js
│   │   │   ├── hierarchy_chart/
│   │   │   │   ├── hierarchy_chart_desktop.js
│   │   │   │   └── hierarchy_chart_mobile.js
│   │   │   ├── hrms.bundle.js
│   │   │   ├── interview.bundle.js
│   │   │   ├── performance/
│   │   │   │   └── performance_feedback.js
│   │   │   ├── performance.bundle.js
│   │   │   ├── salary_slip_deductions_report_filters.js
│   │   │   ├── templates/
│   │   │   │   ├── circular_progress_bar.html
│   │   │   │   ├── employees_with_unmarked_attendance.html
│   │   │   │   ├── feedback_history.html
│   │   │   │   ├── feedback_summary.html
│   │   │   │   ├── interview_feedback.html
│   │   │   │   ├── node_card.html
│   │   │   │   ├── performance_feedback.html
│   │   │   │   └── rating.html
│   │   │   └── utils/
│   │   │       ├── index.js
│   │   │       ├── leave_utils.js
│   │   │       └── payroll_utils.js
│   │   └── scss/
│   │       ├── circular_progress.scss
│   │       ├── feedback.scss
│   │       ├── hierarchy_chart.scss
│   │       └── hrms.bundle.scss
│   ├── regional/
│   │   ├── india/
│   │   │   ├── data/
│   │   │   │   └── salary_components.json
│   │   │   ├── setup.py
│   │   │   └── utils.py
│   │   └── united_arab_emirates/
│   │       └── setup.py
│   ├── setup.py
│   ├── subscription_utils.py
│   ├── templates/
│   │   ├── __init__.py
│   │   ├── emails/
│   │   │   ├── anniversary_reminder.html
│   │   │   ├── birthday_reminder.html
│   │   │   ├── daily_work_summary.html
│   │   │   ├── daily_work_summary.txt
│   │   │   ├── holiday_reminder.html
│   │   │   └── training_event.html
│   │   ├── generators/
│   │   │   └── job_opening.html
│   │   ├── includes/
│   │   │   └── salary_slip_log.html
│   │   └── pages/
│   │       └── __init__.py
│   ├── tests/
│   │   ├── test_utils.py
│   │   └── utils.py
│   ├── uninstall.py
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── custom_method_for_charts.py
│   │   ├── hierarchy_chart.py
│   │   └── holiday_list.py
│   ├── workspace_sidebar/
│   │   ├── expenses.json
│   │   ├── leaves.json
│   │   ├── payroll.json
│   │   ├── people.json
│   │   ├── performance.json
│   │   ├── recruitment.json
│   │   ├── shift_&_attendance.json
│   │   ├── tax_&_benefits.json
│   │   └── tenure.json
│   └── www/
│       ├── __init__.py
│       ├── hrms.py
│       ├── jobs/
│       │   ├── __init__.py
│       │   ├── index.css
│       │   ├── index.html
│       │   ├── index.js
│       │   └── index.py
│       └── roster.py
├── license.txt
├── package.json
├── pyproject.toml
└── roster/
    ├── .gitignore
    ├── index.d.ts
    ├── index.html
    ├── package.json
    ├── postcss.config.js
    ├── src/
    │   ├── App.vue
    │   ├── components/
    │   │   ├── Link.vue
    │   │   ├── MonthViewHeader.vue
    │   │   ├── MonthViewTable.vue
    │   │   ├── NavBar.vue
    │   │   └── ShiftAssignmentDialog.vue
    │   ├── icons/
    │   │   └── FrappeHRLogo.vue
    │   ├── index.css
    │   ├── main.ts
    │   ├── router.ts
    │   ├── utils/
    │   │   ├── dayjs.ts
    │   │   └── index.ts
    │   └── views/
    │       ├── Home.vue
    │       └── MonthView.vue
    ├── tailwind.config.js
    ├── tsconfig.json
    └── vite.config.js
Download .txt
Showing preview only (304K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3107 symbols across 500 files)

FILE: .github/helper/documentation.py
  function uri_validator (line 6) | def uri_validator(x):
  function docs_link_exists (line 11) | def docs_link_exists(body):

FILE: frontend/public/frappe-push-notification.js
  class FrappePushNotification (line 10) | class FrappePushNotification {
    method relayServerBaseURL (line 11) | static get relayServerBaseURL() {
    method constructor (line 33) | constructor(projectName) {
    method initialize (line 57) | async initialize(serviceWorkerRegistration) {
    method appendConfigToServiceWorkerURL (line 75) | async appendConfigToServiceWorkerURL(url, parameter_name = "config") {
    method fetchWebConfig (line 86) | async fetchWebConfig() {
    method fetchVapidPublicKey (line 108) | async fetchVapidPublicKey() {
    method onMessage (line 138) | onMessage(callback) {
    method isNotificationEnabled (line 150) | isNotificationEnabled() {
    method enableNotification (line 160) | async enableNotification() {
    method disableNotification (line 213) | async disableNotification() {
    method registerTokenHandler (line 245) | async registerTokenHandler(token) {
    method unregisterTokenHandler (line 272) | async unregisterTokenHandler(token) {

FILE: frontend/public/sw.js
  function isChrome (line 20) | function isChrome() {

FILE: frontend/src/composables/index.js
  function getFileReader (line 3) | function getFileReader() {
  class FileAttachment (line 9) | class FileAttachment {
    method constructor (line 10) | constructor(fileObj) {
    method upload (line 15) | async upload(documentType, documentName, fieldName) {
    method delete (line 51) | delete() {
  function guessStatusColor (line 74) | async function guessStatusColor(doctype, status) {

FILE: frontend/src/composables/realtime.js
  function useListUpdate (line 5) | function useListUpdate(socket, doctype, callback) {
  function subscribe (line 14) | function subscribe(socket, doctype) {

FILE: frontend/src/composables/workflow.js
  function useWorkflow (line 5) | function useWorkflow(doctype) {

FILE: frontend/src/data/advances.js
  method transform (line 14) | transform(data) {

FILE: frontend/src/data/attendance.js
  method transform (line 57) | transform(data) {
  method transform (line 77) | transform(data) {
  method transform (line 92) | transform(data) {

FILE: frontend/src/data/claims.js
  method transform (line 26) | transform(data) {
  method onSuccess (line 29) | onSuccess() {
  method transform (line 44) | transform(data) {
  method transform (line 54) | transform(data) {

FILE: frontend/src/data/config/requestSummaryFields.js
  constant LEAVE_FIELDS (line 4) | const LEAVE_FIELDS = [
  constant EXPENSE_CLAIM_FIELDS (line 57) | const EXPENSE_CLAIM_FIELDS = [
  constant ATTENDANCE_REQUEST_FIELDS (line 116) | const ATTENDANCE_REQUEST_FIELDS = [
  constant SHIFT_FIELDS (line 154) | const SHIFT_FIELDS = [
  constant SHIFT_REQUEST_FIELDS (line 187) | const SHIFT_REQUEST_FIELDS = [
  constant EMPLOYEE_CHECKIN_FIELDS (line 220) | const EMPLOYEE_CHECKIN_FIELDS = [

FILE: frontend/src/data/currencies.js
  function getCompanyCurrency (line 13) | function getCompanyCurrency(company) {
  function getCompanyCurrencySymbol (line 17) | function getCompanyCurrencySymbol(company) {
  function getCurrencySymbol (line 21) | function getCurrencySymbol(currency) {

FILE: frontend/src/data/employee.js
  method onError (line 7) | onError(error) {

FILE: frontend/src/data/employees.js
  method transform (line 11) | transform(data) {
  method onError (line 20) | onError(error) {
  function getEmployeeInfo (line 27) | function getEmployeeInfo(employeeID) {
  function getEmployeeInfoByUserID (line 33) | function getEmployeeInfoByUserID(userID) {

FILE: frontend/src/data/leaves.js
  method transform (line 31) | transform(data) {
  method onSuccess (line 34) | onSuccess() {
  method transform (line 49) | transform(data) {

FILE: frontend/src/data/notifications.js
  method onSuccess (line 26) | onSuccess() {

FILE: frontend/src/data/session.js
  function sessionUser (line 7) | function sessionUser() {
  function handleLogin (line 16) | function handleLogin(response) {
  method onSuccess (line 39) | onSuccess() {

FILE: frontend/src/data/user.js
  method onError (line 7) | onError(error) {

FILE: frontend/src/plugins/translationsPlugin.js
  function makeTranslationFunction (line 1) | function makeTranslationFunction() {
  method isReady (line 70) | async isReady() {
  method install (line 73) | install(/** @type {import('vue').App} */ app, options) {

FILE: frontend/src/socket.js
  function initSocket (line 7) | function initSocket() {

FILE: frontend/src/utils/commonUtils.js
  function useDownloadPDF (line 3) | function useDownloadPDF() {

FILE: frontend/vite.config.js
  function getProxyOptions (line 92) | function getProxyOptions() {
  function getCommonSiteConfig (line 111) | function getCommonSiteConfig() {

FILE: hrms/__init__.py
  function refetch_resource (line 6) | def refetch_resource(cache_key: str | list, user=None):

FILE: hrms/api/__init__.py
  function get_current_user_info (line 31) | def get_current_user_info() -> dict:
  function get_current_employee_info (line 42) | def get_current_employee_info() -> dict:
  function get_all_employees (line 63) | def get_all_employees() -> list[dict]:
  function get_current_employee (line 81) | def get_current_employee() -> str:
  function get_hr_settings (line 90) | def get_hr_settings() -> dict:
  function get_unread_notifications_count (line 100) | def get_unread_notifications_count() -> int:
  function mark_all_notifications_as_read (line 108) | def mark_all_notifications_as_read() -> None:
  function are_push_notifications_enabled (line 119) | def are_push_notifications_enabled() -> bool:
  function get_attendance_calendar_events (line 129) | def get_attendance_calendar_events(from_date: str, to_date: str) -> dict...
  function get_attendance_for_calendar (line 147) | def get_attendance_for_calendar(employee: str, from_date: str, to_date: ...
  function get_holidays_for_calendar (line 156) | def get_holidays_for_calendar(employee: str, from_date: str, to_date: st...
  function get_shift_requests (line 168) | def get_shift_requests(
  function get_attendance_requests (line 207) | def get_attendance_requests(
  function get_filters (line 244) | def get_filters(
  function get_shift_request_approvers (line 275) | def get_shift_request_approvers(employee: str) -> str | list[str]:
  function get_shifts (line 305) | def get_shifts() -> list[dict[str, str]]:
  function get_leave_applications (line 332) | def get_leave_applications(
  function get_leave_balance_map (line 377) | def get_leave_balance_map() -> dict[str, dict[str, float]]:
  function get_holidays_for_employee (line 405) | def get_holidays_for_employee(employee: str) -> list[dict]:
  function get_leave_approval_details (line 425) | def get_leave_approval_details(employee: str) -> dict:
  function get_department_approvers (line 455) | def get_department_approvers(department: str, parentfield: str) -> list[...
  function get_leave_types (line 484) | def get_leave_types(employee: str, date: str) -> list:
  function get_expense_claims (line 497) | def get_expense_claims(
  function get_expense_claim_summary (line 540) | def get_expense_claim_summary() -> dict:
  function get_expense_type_description (line 588) | def get_expense_type_description(expense_type: str) -> str:
  function get_expense_claim_types (line 593) | def get_expense_claim_types() -> list[dict]:
  function get_expense_approval_details (line 600) | def get_expense_approval_details(employee: str) -> dict:
  function get_employee_advance_balance (line 630) | def get_employee_advance_balance() -> list[dict]:
  function get_advance_account (line 660) | def get_advance_account(company: str) -> str | None:
  function get_company_currencies (line 666) | def get_company_currencies() -> dict:
  function get_currency_symbols (line 687) | def get_currency_symbols() -> dict:
  function get_company_cost_center_and_expense_account (line 696) | def get_company_cost_center_and_expense_account(company: str) -> dict:
  function get_doctype_fields (line 704) | def get_doctype_fields(doctype: str) -> list[dict]:
  function get_doctype_states (line 714) | def get_doctype_states(doctype: str) -> dict:
  function get_attachments (line 721) | def get_attachments(dt: str, dn: str):
  function upload_base64_file (line 730) | def upload_base64_file(content, filename, dt=None, dn=None, fieldname=No...
  function delete_attachment (line 770) | def delete_attachment(filename: str):
  function _download_pdf (line 775) | def _download_pdf(doctype: str, docname: str) -> str:
  function get_workflow (line 795) | def get_workflow(doctype: str) -> dict:
  function get_workflow_state_field (line 802) | def get_workflow_state_field(doctype: str) -> str | None:
  function get_allowed_states_for_workflow (line 818) | def get_allowed_states_for_workflow(workflow: dict, user_id: str) -> lis...
  function get_permitted_fields_for_write (line 825) | def get_permitted_fields_for_write(doctype: str) -> list[str]:

FILE: hrms/api/oauth.py
  function oauth_providers (line 5) | def oauth_providers():

FILE: hrms/api/roster.py
  function get_default_company (line 13) | def get_default_company() -> str:
  function get_events (line 18) | def get_events(
  function get_schedule_from_assignment (line 36) | def get_schedule_from_assignment(shift_schedule_assignment: str):
  function create_shift_schedule_assignment (line 46) | def create_shift_schedule_assignment(
  function delete_shift_schedule_assignment (line 79) | def delete_shift_schedule_assignment(shift_schedule_assignment: str) -> ...
  function swap_shift (line 91) | def swap_shift(
  function break_shift (line 129) | def break_shift(assignment: str | ShiftAssignment, date: str) -> None:
  function insert_shift (line 159) | def insert_shift(
  function get_holidays (line 195) | def get_holidays(month_start: str, month_end: str, employee_filters: dic...
  function get_leaves (line 215) | def get_leaves(month_start: str, month_end: str, employee_filters: dict[...
  function get_shifts (line 244) | def get_shifts(
  function group_by_employee (line 286) | def group_by_employee(events: list[dict]) -> dict[str, list[dict]]:

FILE: hrms/api/system_settings.py
  function get_user_pass_login_disabled (line 5) | def get_user_pass_login_disabled():

FILE: hrms/config/desktop.py
  function get_data (line 4) | def get_data():

FILE: hrms/config/docs.py
  function get_context (line 10) | def get_context(context):

FILE: hrms/controllers/employee_boarding_controller.py
  class EmployeeBoardingController (line 14) | class EmployeeBoardingController(Document):
    method validate (line 20) | def validate(self):
    method on_submit (line 26) | def on_submit(self):
    method create_task_and_notify_user (line 51) | def create_task_and_notify_user(self):
    method get_holiday_list (line 102) | def get_holiday_list(self):
    method get_task_dates (line 114) | def get_task_dates(self, activity, holiday_list):
    method update_if_holiday (line 127) | def update_if_holiday(self, date, holiday_list):
    method assign_task_to_users (line 132) | def assign_task_to_users(self, task, users):
    method on_cancel (line 143) | def on_cancel(self):
  function get_onboarding_details (line 159) | def get_onboarding_details(parent, parenttype):
  function update_employee_boarding_status (line 177) | def update_employee_boarding_status(project, event=None):
  function update_task (line 196) | def update_task(task, event=None):

FILE: hrms/controllers/employee_reminders.py
  function send_reminders_in_advance_weekly (line 16) | def send_reminders_in_advance_weekly():
  function send_reminders_in_advance_monthly (line 25) | def send_reminders_in_advance_monthly():
  function send_advance_holiday_reminders (line 34) | def send_advance_holiday_reminders(frequency):
  function send_holidays_reminder_in_advance (line 57) | def send_holidays_reminder_in_advance(employee, holidays):
  function send_birthday_reminders (line 87) | def send_birthday_reminders():
  function get_birthday_reminder_text_and_message (line 114) | def get_birthday_reminder_text_and_message(birthday_persons):
  function send_birthday_reminder (line 130) | def send_birthday_reminder(recipients, reminder_text, birthday_persons, ...
  function get_employees_who_are_born_today (line 145) | def get_employees_who_are_born_today():
  function get_employees_having_an_event_today (line 150) | def get_employees_having_an_event_today(event_type):
  function send_work_anniversary_reminders (line 207) | def send_work_anniversary_reminders():
  function get_work_anniversary_reminder_text (line 237) | def get_work_anniversary_reminder_text(anniversary_persons: list) -> str:
  function get_year_label (line 264) | def get_year_label(years: int) -> str:
  function send_work_anniversary_reminder (line 268) | def send_work_anniversary_reminder(
  function get_sender_email (line 289) | def get_sender_email() -> str | None:

FILE: hrms/controllers/tests/test_employee_reminders.py
  class TestEmployeeReminders (line 20) | class TestEmployeeReminders(HRMSTestSuite):
    method setUp (line 21) | def setUp(self):
    method get_test_holiday_dates (line 70) | def get_test_holiday_dates(cls):
    method test_is_holiday (line 81) | def test_is_holiday(self):
    method test_birthday_reminders (line 99) | def test_birthday_reminders(self):
    method test_work_anniversary_reminders (line 123) | def test_work_anniversary_reminders(self):
    method test_work_anniversary_reminder_not_sent_for_0_years (line 152) | def test_work_anniversary_reminder_not_sent_for_0_years(self):
    method test_send_holidays_reminder_in_advance (line 169) | def test_send_holidays_reminder_in_advance(self):
    method test_advance_holiday_reminders_monthly (line 186) | def test_advance_holiday_reminders_monthly(self):
    method test_advance_holiday_reminders_weekly (line 213) | def test_advance_holiday_reminders_weekly(self):
    method test_reminder_not_sent_if_no_holdays (line 240) | def test_reminder_not_sent_if_no_holdays(self):
  function setup_hr_settings (line 256) | def setup_hr_settings(frequency=None):

FILE: hrms/hr/dashboard_chart_source/employees_by_age/employees_by_age.py
  function get_data (line 15) | def get_data(
  function get_ranges (line 47) | def get_ranges() -> list[tuple[int, int]]:
  function get_age_list (line 58) | def get_age_list(employees) -> list[int]:
  function get_employees_by_age (line 69) | def get_employees_by_age(age_list, ranges) -> tuple[list[str], list[int]]:

FILE: hrms/hr/dashboard_chart_source/hiring_vs_attrition_count/hiring_vs_attrition_count.py
  function get_data (line 15) | def get_data(
  function get_records (line 61) | def get_records(from_date: str, to_date: str, datefield: str, company: s...

FILE: hrms/hr/doctype/appointment_letter/appointment_letter.py
  class AppointmentLetter (line 9) | class AppointmentLetter(Document):
  function get_appointment_letter_details (line 36) | def get_appointment_letter_details(template: str) -> list:

FILE: hrms/hr/doctype/appointment_letter/test_appointment_letter.py
  class TestAppointmentLetter (line 8) | class TestAppointmentLetter(HRMSTestSuite):

FILE: hrms/hr/doctype/appointment_letter_content/appointment_letter_content.py
  class AppointmentLettercontent (line 9) | class AppointmentLettercontent(Document):

FILE: hrms/hr/doctype/appointment_letter_template/appointment_letter_template.py
  class AppointmentLetterTemplate (line 9) | class AppointmentLetterTemplate(Document):

FILE: hrms/hr/doctype/appointment_letter_template/test_appointment_letter_template.py
  class TestAppointmentLetterTemplate (line 8) | class TestAppointmentLetterTemplate(HRMSTestSuite):

FILE: hrms/hr/doctype/appraisal/appraisal.js
  method refresh (line 5) | refresh(frm) {
  method appraisal_template (line 16) | appraisal_template(frm) {
  method appraisal_cycle (line 25) | appraisal_cycle(frm) {
  method add_custom_buttons (line 55) | add_custom_buttons(frm) {
  method show_feedback_history (line 66) | show_feedback_history(frm) {
  method setup_chart (line 76) | setup_chart(frm) {
  method calculate_total (line 115) | calculate_total(frm) {
  method score (line 127) | score(frm, cdt, cdn) {
  method per_weightage (line 139) | per_weightage(frm, cdt, cdn) {
  method goals_remove (line 143) | goals_remove(frm, cdt, cdn) {
  method set_score_earned (line 147) | set_score_earned(frm, cdt, cdn) {

FILE: hrms/hr/doctype/appraisal/appraisal.py
  class Appraisal (line 16) | class Appraisal(Document, AppraisalMixin):
    method validate (line 54) | def validate(self):
    method validate_duplicate (line 68) | def validate_duplicate(self):
    method set_kra_evaluation_method (line 102) | def set_kra_evaluation_method(self):
    method set_appraisal_template (line 114) | def set_appraisal_template(self):
    method set_kras_and_rating_criteria (line 133) | def set_kras_and_rating_criteria(self):
    method calculate_total_score (line 165) | def calculate_total_score(self):
    method calculate_self_appraisal_score (line 201) | def calculate_self_appraisal_score(self):
    method calculate_avg_feedback_score (line 211) | def calculate_avg_feedback_score(self, update=False):
    method calculate_final_score (line 224) | def calculate_final_score(self):
    method add_feedback (line 250) | def add_feedback(self, feedback: str, feedback_ratings: list) -> Docum...
    method set_goal_score (line 276) | def set_goal_score(self, update=False):
  function get_feedback_history (line 309) | def get_feedback_history(employee: str, appraisal: str) -> dict:
  function get_kras_for_employee (line 363) | def get_kras_for_employee(

FILE: hrms/hr/doctype/appraisal/test_appraisal.py
  class TestAppraisal (line 20) | class TestAppraisal(HRMSTestSuite):
    method setUp (line 21) | def setUp(self):
    method test_validate_duplicate (line 36) | def test_validate_duplicate(self):
    method test_manual_kra_rating (line 51) | def test_manual_kra_rating(self):
    method test_final_score (line 70) | def test_final_score(self):
    method test_final_score_using_formula (line 77) | def test_final_score_using_formula(self):
    method setup_appraisal (line 92) | def setup_appraisal(self, cycle):
    method test_goal_score (line 123) | def test_goal_score(self):
    method test_goal_score_after_parent_goal_change (line 165) | def test_goal_score_after_parent_goal_change(self):
    method test_goal_score_after_kra_change (line 219) | def test_goal_score_after_kra_change(self):
    method test_goal_score_after_goal_deletion (line 243) | def test_goal_score_after_goal_deletion(self):
    method test_calculate_self_appraisal_score (line 261) | def test_calculate_self_appraisal_score(self):
    method test_cycle_completion (line 277) | def test_cycle_completion(self):
    method test_cycle_summary (line 301) | def test_cycle_summary(self):

FILE: hrms/hr/doctype/appraisal_cycle/appraisal_cycle.js
  method refresh (line 5) | refresh(frm) {
  method show_custom_buttons (line 19) | show_custom_buttons(frm) {
  method get_employees (line 104) | get_employees(frm) {
  method create_appraisals (line 117) | create_appraisals(frm) {
  method complete_cycle (line 129) | complete_cycle(frm) {
  method show_appraisal_summary (line 149) | show_appraisal_summary(frm) {

FILE: hrms/hr/doctype/appraisal_cycle/appraisal_cycle.py
  class AppraisalCycle (line 11) | class AppraisalCycle(Document):
    method onload (line 37) | def onload(self):
    method validate (line 40) | def validate(self):
    method validate_evaluation_method_change (line 44) | def validate_evaluation_method_change(self):
    method check_if_appraisals_exist (line 56) | def check_if_appraisals_exist(self):
    method set_employees (line 63) | def set_employees(self):
    method get_employees_for_appraisal (line 96) | def get_employees_for_appraisal(self):
    method get_appraisal_template_map (line 122) | def get_appraisal_template_map(self):
    method create_appraisals (line 132) | def create_appraisals(self):
    method show_missing_template_message (line 159) | def show_missing_template_message(self, raise_exception=False):
    method complete_cycle (line 171) | def complete_cycle(self):
  function create_appraisals_for_cycle (line 189) | def create_appraisals_for_cycle(appraisal_cycle: AppraisalCycle, publish...
  function validate_active_appraisal_cycle (line 224) | def validate_active_appraisal_cycle(appraisal_cycle: str) -> None:
  function get_appraisal_cycle_summary (line 236) | def get_appraisal_cycle_summary(cycle_name: str) -> dict:
  function get_employees_without_goals (line 251) | def get_employees_without_goals(cycle_name: str) -> int:
  function get_employees_without_feedback (line 277) | def get_employees_without_feedback(cycle_name: str | None = None) -> int:

FILE: hrms/hr/doctype/appraisal_cycle/test_appraisal_cycle.py
  class TestAppraisalCycle (line 14) | class TestAppraisalCycle(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_set_employees (line 28) | def test_set_employees(self):
    method test_create_appraisals (line 34) | def test_create_appraisals(self):
  function create_appraisal_cycle (line 55) | def create_appraisal_cycle(**args):

FILE: hrms/hr/doctype/appraisal_goal/appraisal_goal.py
  class AppraisalGoal (line 8) | class AppraisalGoal(Document):

FILE: hrms/hr/doctype/appraisal_kra/appraisal_kra.py
  class AppraisalKRA (line 8) | class AppraisalKRA(Document):

FILE: hrms/hr/doctype/appraisal_template/appraisal_template.js
  method setup (line 5) | setup(frm) {

FILE: hrms/hr/doctype/appraisal_template/appraisal_template.py
  class AppraisalTemplate (line 13) | class AppraisalTemplate(Document, AppraisalMixin):
    method validate (line 31) | def validate(self):

FILE: hrms/hr/doctype/appraisal_template/test_appraisal_template.py
  class TestAppraisalTemplate (line 9) | class TestAppraisalTemplate(HRMSTestSuite):
    method test_incorrect_weightage_allocation (line 10) | def test_incorrect_weightage_allocation(self):
  function create_kras (line 21) | def create_kras(kras):
  function create_criteria (line 32) | def create_criteria(criteria):
  function create_appraisal_template (line 43) | def create_appraisal_template(title=None, kras=None, rating_criteria=None):

FILE: hrms/hr/doctype/appraisal_template_goal/appraisal_template_goal.py
  class AppraisalTemplateGoal (line 8) | class AppraisalTemplateGoal(Document):

FILE: hrms/hr/doctype/appraisee/appraisee.py
  class Appraisee (line 8) | class Appraisee(Document):

FILE: hrms/hr/doctype/attendance/attendance.js
  method refresh (line 5) | refresh(frm) {

FILE: hrms/hr/doctype/attendance/attendance.py
  class DuplicateAttendanceError (line 33) | class DuplicateAttendanceError(frappe.ValidationError):
  class OverlappingShiftAttendanceError (line 37) | class OverlappingShiftAttendanceError(frappe.ValidationError):
  class Attendance (line 41) | class Attendance(Document):
    method before_insert (line 74) | def before_insert(self):
    method validate (line 78) | def validate(self):
    method on_cancel (line 89) | def on_cancel(self):
    method validate_attendance_date (line 92) | def validate_attendance_date(self):
    method validate_duplicate_record (line 104) | def validate_duplicate_record(self):
    method get_duplicate_attendance_record (line 118) | def get_duplicate_attendance_record(self) -> str | None:
    method validate_overlapping_shift_attendance (line 150) | def validate_overlapping_shift_attendance(self):
    method get_overlapping_shift_attendance (line 164) | def get_overlapping_shift_attendance(self) -> dict:
    method validate_employee_status (line 187) | def validate_employee_status(self):
    method check_leave_record (line 191) | def check_leave_record(self):
    method validate_employee (line 243) | def validate_employee(self):
    method unlink_attendance_from_checkins (line 250) | def unlink_attendance_from_checkins(self):
    method on_update (line 277) | def on_update(self):
    method after_delete (line 280) | def after_delete(self):
    method publish_update (line 283) | def publish_update(self):
  function get_events (line 289) | def get_events(start: date | str, end: date | str, filters: str | list |...
  function add_attendance (line 306) | def add_attendance(filters):
  function add_holidays (line 324) | def add_holidays(events, start, end, employee=None):
  function mark_attendance (line 341) | def mark_attendance(
  function mark_bulk_attendance (line 379) | def mark_bulk_attendance(data: str | dict):
  function process_bulk_attendance_in_batches (line 407) | def process_bulk_attendance_in_batches(data, chunk_size=20):
  function get_unmarked_days (line 432) | def get_unmarked_days(

FILE: hrms/hr/doctype/attendance/attendance_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/attendance/attendance_list.js
  method primary_action (line 98) | primary_action(data) {

FILE: hrms/hr/doctype/attendance/test_attendance.py
  class TestAttendance (line 38) | class TestAttendance(HRMSTestSuite):
    method setUp (line 39) | def setUp(self):
    method test_duplicate_attendance (line 42) | def test_duplicate_attendance(self):
    method test_duplicate_attendance_with_shift (line 59) | def test_duplicate_attendance_with_shift(self):
    method test_overlapping_shift_attendance_validation (line 95) | def test_overlapping_shift_attendance_validation(self):
    method test_allow_attendance_with_different_shifts (line 120) | def test_allow_attendance_with_different_shifts(self):
    method test_mark_absent (line 142) | def test_mark_absent(self):
    method test_unmarked_days (line 152) | def test_unmarked_days(self):
    method test_unmarked_days_excluding_holidays (line 178) | def test_unmarked_days_excluding_holidays(self):
    method test_unmarked_days_excluding_holidays_across_two_holiday_list_assignments (line 202) | def test_unmarked_days_excluding_holidays_across_two_holiday_list_assi...
    method test_unmarked_days_as_per_joining_and_relieving_dates (line 224) | def test_unmarked_days_as_per_joining_and_relieving_dates(self):
    method test_duplicate_attendance_when_created_from_checkins_and_tool (line 254) | def test_duplicate_attendance_when_created_from_checkins_and_tool(self):
    method test_get_events_returns_attendance (line 275) | def test_get_events_returns_attendance(self):
    method test_bulk_attendance_marking_through_bg (line 299) | def test_bulk_attendance_marking_through_bg(self):
    method tearDown (line 329) | def tearDown(self):

FILE: hrms/hr/doctype/attendance_request/attendance_request.js
  method refresh (line 4) | refresh(frm) {
  method show_attendance_warnings (line 8) | show_attendance_warnings(frm) {

FILE: hrms/hr/doctype/attendance_request/attendance_request.py
  class OverlappingAttendanceRequestError (line 16) | class OverlappingAttendanceRequestError(frappe.ValidationError):
  class AttendanceRequest (line 20) | class AttendanceRequest(Document):
    method validate (line 44) | def validate(self):
    method validate_half_day (line 51) | def validate_half_day(self):
    method validate_no_attendance_to_create (line 56) | def validate_no_attendance_to_create(self):
    method validate_request_overlap (line 69) | def validate_request_overlap(self):
    method throw_overlap_error (line 89) | def throw_overlap_error(self, overlapping_request: str):
    method on_submit (line 97) | def on_submit(self):
    method on_cancel (line 100) | def on_cancel(self):
    method create_attendance_records (line 109) | def create_attendance_records(self):
    method create_or_update_attendance (line 116) | def create_or_update_attendance(self, date: str):
    method should_mark_attendance (line 159) | def should_mark_attendance(self, attendance_date: str) -> bool:
    method has_leave_record (line 180) | def has_leave_record(self, attendance_date: str) -> str | None:
    method get_attendance_doc (line 192) | def get_attendance_doc(self, attendance_date: str) -> str | None:
    method get_attendance_status (line 203) | def get_attendance_status(self, attendance_date: str) -> str:
    method status_unchanged (line 211) | def status_unchanged(self, attendance_date):
    method on_update (line 218) | def on_update(self):
    method after_delete (line 221) | def after_delete(self):
    method publish_update (line 224) | def publish_update(self):
    method get_attendance_warnings (line 230) | def get_attendance_warnings(self) -> list:

FILE: hrms/hr/doctype/attendance_request/attendance_request_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/attendance_request/test_attendance_request.py
  class TestAttendanceRequest (line 18) | class TestAttendanceRequest(HRMSTestSuite):
    method setUp (line 19) | def setUp(self):
    method test_attendance_request_overlap (line 23) | def test_attendance_request_overlap(self):
    method test_on_duty_attendance_request (line 52) | def test_on_duty_attendance_request(self):
    method test_work_from_home_attendance_request (line 75) | def test_work_from_home_attendance_request(self):
    method test_overwrite_attendance (line 89) | def test_overwrite_attendance(self):
    method test_skip_attendance_on_holiday (line 101) | def test_skip_attendance_on_holiday(self):
    method test_skip_attendance_on_leave (line 116) | def test_skip_attendance_on_leave(self):
    method test_include_holidays_check (line 140) | def test_include_holidays_check(self):
    method get_attendance_records (line 159) | def get_attendance_records(self, attendance_request: str) -> list[dict]:
    method test_validate_no_attendance_to_create (line 168) | def test_validate_no_attendance_to_create(self):
    method test_half_day_status_change (line 195) | def test_half_day_status_change(self):
    method test_half_day_status_change_when_existing_attendance_is_updated (line 216) | def test_half_day_status_change_when_existing_attendance_is_updated(se...
  function get_employee (line 248) | def get_employee():
  function create_attendance_request (line 252) | def create_attendance_request(**args: dict) -> dict:

FILE: hrms/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
  class CompensatoryLeaveRequest (line 21) | class CompensatoryLeaveRequest(Document):
    method validate (line 43) | def validate(self):
    method validate_attendance (line 57) | def validate_attendance(self):
    method validate_holidays (line 81) | def validate_holidays(self):
    method on_submit (line 94) | def on_submit(self):
    method on_cancel (line 128) | def on_cancel(self):
    method get_existing_allocation (line 147) | def get_existing_allocation(self, comp_leave_valid_from: datetime.date...
    method create_leave_allocation (line 163) | def create_leave_allocation(self, leave_period, date_difference):

FILE: hrms/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
  class TestCompensatoryLeaveRequest (line 18) | class TestCompensatoryLeaveRequest(HRMSTestSuite):
    method setUp (line 19) | def setUp(self):
    method test_leave_balance_on_submit (line 27) | def test_leave_balance_on_submit(self):
    method test_leave_balance_on_cancel (line 41) | def test_leave_balance_on_cancel(self):
    method test_allocation_update_on_submit (line 60) | def test_allocation_update_on_submit(self):
    method test_allocation_update_on_submit_on_multiple_allocations (line 88) | def test_allocation_update_on_submit_on_multiple_allocations(self):
    method test_creation_of_leave_ledger_entry_on_submit (line 136) | def test_creation_of_leave_ledger_entry_on_submit(self):
    method test_half_day_compensatory_leave (line 162) | def test_half_day_compensatory_leave(self):
    method test_request_on_leave_period_boundary (line 193) | def test_request_on_leave_period_boundary(self):
  function get_compensatory_leave_request (line 222) | def get_compensatory_leave_request(employee, leave_date=None):
  function mark_attendance (line 251) | def mark_attendance(employee, date=None, status="Present", half_day_stat...
  function create_holiday_list (line 271) | def create_holiday_list(from_date=None, to_date=None):

FILE: hrms/hr/doctype/daily_work_summary/daily_work_summary.py
  class DailyWorkSummary (line 13) | class DailyWorkSummary(Document):
    method send_mails (line 27) | def send_mails(self, dws_group, emails):
    method send_summary (line 44) | def send_summary(self):
    method get_message_details (line 59) | def get_message_details(self):
  function get_user_emails_from_group (line 119) | def get_user_emails_from_group(group):
  function get_users_email (line 132) | def get_users_email(doc):

FILE: hrms/hr/doctype/daily_work_summary/test_daily_work_summary.py
  class TestDailyWorkSummary (line 14) | class TestDailyWorkSummary(HRMSTestSuite):
    method test_email_trigger (line 15) | def test_email_trigger(self):
    method test_email_trigger_failed (line 24) | def test_email_trigger_failed(self):
    method test_incoming (line 37) | def test_incoming(self):
    method setup_and_prepare_test (line 62) | def setup_and_prepare_test(self, hour=None):
    method setup_groups (line 85) | def setup_groups(self, hour=None):

FILE: hrms/hr/doctype/daily_work_summary_group/daily_work_summary_group.py
  class DailyWorkSummaryGroup (line 15) | class DailyWorkSummaryGroup(Document):
    method validate (line 61) | def validate(self):
  function trigger_emails (line 69) | def trigger_emails():
  function is_current_hour (line 89) | def is_current_hour(hour):
  function send_summary (line 93) | def send_summary():
  function is_incoming_account_enabled (line 100) | def is_incoming_account_enabled():

FILE: hrms/hr/doctype/daily_work_summary_group_user/daily_work_summary_group_user.py
  class DailyWorkSummaryGroupUser (line 8) | class DailyWorkSummaryGroupUser(Document):

FILE: hrms/hr/doctype/department_approver/department_approver.py
  class DepartmentApprover (line 11) | class DepartmentApprover(Document):
  function get_approvers (line 31) | def get_approvers(doctype: str, txt: str, searchfield: str, start: int, ...

FILE: hrms/hr/doctype/designation_skill/designation_skill.py
  class DesignationSkill (line 9) | class DesignationSkill(Document):

FILE: hrms/hr/doctype/earned_leave_schedule/earned_leave_schedule.py
  class EarnedLeaveSchedule (line 8) | class EarnedLeaveSchedule(Document):

FILE: hrms/hr/doctype/employee_advance/employee_advance.py
  class EmployeeAdvanceOverPayment (line 18) | class EmployeeAdvanceOverPayment(frappe.ValidationError):
  class EmployeeAdvance (line 22) | class EmployeeAdvance(Document):
    method onload (line 54) | def onload(self):
    method validate (line 59) | def validate(self):
    method before_submit (line 66) | def before_submit(self):
    method on_cancel (line 98) | def on_cancel(self):
    method on_update (line 103) | def on_update(self):
    method after_delete (line 106) | def after_delete(self):
    method publish_update (line 109) | def publish_update(self):
    method validate_advance_account_type (line 113) | def validate_advance_account_type(self):
    method validate_advance_account_currency (line 125) | def validate_advance_account_currency(self):
    method set_status (line 135) | def set_status(self, update=False):
    method on_discard (line 174) | def on_discard(self):
    method set_total_advance_paid (line 177) | def set_total_advance_paid(self):
    method update_claimed_amount (line 245) | def update_claimed_amount(self):
    method set_pending_amount (line 265) | def set_pending_amount(self):
    method check_linked_payment_entry (line 278) | def check_linked_payment_entry(self):
  function make_bank_entry (line 290) | def make_bank_entry(dt: str, dn: str) -> dict:
  function create_return_through_additional_salary (line 331) | def create_return_through_additional_salary(doc: str | dict | Document) ...
  function make_return_entry (line 350) | def make_return_entry(
  function get_same_currency_bank_cash_account (line 402) | def get_same_currency_bank_cash_account(company, currency, mode_of_payme...
  function get_voucher_type (line 437) | def get_voucher_type(mode_of_payment=None):

FILE: hrms/hr/doctype/employee_advance/employee_advance_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/employee_advance/test_employee_advance.py
  class TestEmployeeAdvance (line 27) | class TestEmployeeAdvance(HRMSTestSuite):
    method setUp (line 28) | def setUp(self):
    method test_paid_amount_and_status (line 34) | def test_paid_amount_and_status(self):
    method test_paid_amount_on_pe_cancellation (line 50) | def test_paid_amount_on_pe_cancellation(self):
    method test_claimed_status (line 72) | def test_claimed_status(self):
    method test_partly_claimed_and_returned_status (line 103) | def test_partly_claimed_and_returned_status(self):
    method test_repay_unclaimed_amount_from_salary (line 165) | def test_repay_unclaimed_amount_from_salary(self):
    method test_payment_entry_against_advance (line 210) | def test_payment_entry_against_advance(self):
    method test_precision (line 229) | def test_precision(self):
    method test_pending_amount (line 267) | def test_pending_amount(self):
    method test_unlink_payment_entries (line 285) | def test_unlink_payment_entries(self):
    method test_employee_advance_when_different_company_currency (line 302) | def test_employee_advance_when_different_company_currency(self):
    method test_employee_advance_when_different_account_currency (line 316) | def test_employee_advance_when_different_account_currency(self):
    method test_employee_advance_when_different_advance_currency (line 328) | def test_employee_advance_when_different_advance_currency(self):
    method update_company_in_fiscal_year (line 335) | def update_company_in_fiscal_year(self):
    method test_multicurrency_advance (line 344) | def test_multicurrency_advance(self):
    method test_status_on_discard (line 367) | def test_status_on_discard(self):
  function make_journal_entry_for_advance (line 378) | def make_journal_entry_for_advance(advance):
  function make_payment_entry (line 388) | def make_payment_entry(advance, amount=None):
  function make_employee_advance (line 402) | def make_employee_advance(employee_name, args=None, do_not_submit=False):
  function get_advances_for_claim (line 428) | def get_advances_for_claim(claim, advance_name, amount=None):
  function create_advance_account (line 438) | def create_advance_account(account_name, account_currency):

FILE: hrms/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
  method refresh (line 2) | refresh(frm) {
  method onload (line 9) | onload(frm) {
  method reset_tool_actions (line 19) | reset_tool_actions(frm) {
  method reset_attendance_fields (line 29) | reset_attendance_fields(frm) {
  method load_employees (line 37) | load_employees(frm) {
  method show_employees_to_mark (line 108) | show_employees_to_mark(frm, html_fieldname, multicheck_fieldname, employ...
  method show_marked_employees (line 136) | show_marked_employees(frm, marked_employees) {
  method get_columns_for_marked_attendance_table (line 146) | get_columns_for_marked_attendance_table() {
  method show_marked_employees (line 191) | show_marked_employees(frm, marked_employees) {
  method render_datatable (line 207) | render_datatable(frm, data, summary_wrapper) {
  method set_primary_action (line 229) | set_primary_action(frm) {
  method mark_full_day_attendance (line 270) | mark_full_day_attendance(frm, employees_to_mark_full_day, employees_to_m...

FILE: hrms/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
  class EmployeeAttendanceTool (line 13) | class EmployeeAttendanceTool(Document):
  function get_employees (line 41) | def get_employees(
  function _get_unmarked_attendance (line 105) | def _get_unmarked_attendance(employee_list: list[dict], attendance_list:...
  function _get_unmarked_attendance_with_shift (line 116) | def _get_unmarked_attendance_with_shift(unmarked_attendance, shift, date):
  function mark_employee_attendance (line 148) | def mark_employee_attendance(

FILE: hrms/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.py
  class TestEmployeeAttendanceTool (line 24) | class TestEmployeeAttendanceTool(HRMSTestSuite):
    method setUp (line 25) | def setUp(self):
    method test_get_employee_attendance (line 35) | def test_get_employee_attendance(self):
    method test_mark_employee_attendance (line 54) | def test_mark_employee_attendance(self):
    method test_get_employees_for_half_day_attendance (line 77) | def test_get_employees_for_half_day_attendance(self):
    method test_update_half_day_attendance (line 109) | def test_update_half_day_attendance(self):
    method test_get_unmarked_attendance_with_shift (line 162) | def test_get_unmarked_attendance_with_shift(self):
  function create_leave_allocation (line 232) | def create_leave_allocation(employee, leave_type):

FILE: hrms/hr/doctype/employee_boarding_activity/employee_boarding_activity.py
  class EmployeeBoardingActivity (line 8) | class EmployeeBoardingActivity(Document):

FILE: hrms/hr/doctype/employee_checkin/employee_checkin.js
  method add_fetch_shift_button (line 31) | add_fetch_shift_button(frm) {

FILE: hrms/hr/doctype/employee_checkin/employee_checkin.py
  class CheckinRadiusExceededError (line 20) | class CheckinRadiusExceededError(frappe.ValidationError):
  class EmployeeCheckin (line 24) | class EmployeeCheckin(Document):
    method before_validate (line 51) | def before_validate(self):
    method validate (line 54) | def validate(self):
    method validate_duplicate_log (line 62) | def validate_duplicate_log(self):
    method validate_time_change (line 78) | def validate_time_change(self):
    method set_geolocation (line 88) | def set_geolocation(self):
    method fetch_shift (line 92) | def fetch_shift(self):
    method validate_distance_from_shift_location (line 122) | def validate_distance_from_shift_location(self):
  function add_log_based_on_employee_field (line 160) | def add_log_based_on_employee_field(
  function bulk_fetch_shift (line 216) | def bulk_fetch_shift(checkins: list[str] | str) -> None:
  function mark_attendance_and_link_log (line 226) | def mark_attendance_and_link_log(
  function create_or_update_attendance (line 285) | def create_or_update_attendance(
  function get_overtime_data (line 348) | def get_overtime_data(shift_name, working_hours):
  function get_existing_half_day_attendance (line 375) | def get_existing_half_day_attendance(employee, attendance_date):
  function calculate_working_hours (line 393) | def calculate_working_hours(logs, check_in_out_type, working_hours_calc_...
  function time_diff_in_hours (line 453) | def time_diff_in_hours(start, end):
  function find_index_in_dict (line 457) | def find_index_in_dict(dict_list, key, value):
  function handle_attendance_exception (line 461) | def handle_attendance_exception(log_names: list, error_message: str):
  function add_comment_in_checkins (line 468) | def add_comment_in_checkins(log_names: list, error_message: str):
  function skip_attendance_in_checkins (line 485) | def skip_attendance_in_checkins(log_names: list):
  function update_attendance_in_checkins (line 494) | def update_attendance_in_checkins(log_names: list, attendance_id: str):
  function calculate_time_difference (line 503) | def calculate_time_difference(start_time, end_time):

FILE: hrms/hr/doctype/employee_checkin/test_employee_checkin.py
  class TestEmployeeCheckin (line 33) | class TestEmployeeCheckin(HRMSTestSuite):
    method setUp (line 34) | def setUp(self):
    method test_geolocation_tracking (line 40) | def test_geolocation_tracking(self):
    method test_add_log_based_on_employee_field (line 69) | def test_add_log_based_on_employee_field(self):
    method test_mark_attendance_and_link_log (line 82) | def test_mark_attendance_and_link_log(self):
    method test_unlink_attendance_on_cancellation (line 107) | def test_unlink_attendance_on_cancellation(self):
    method test_calculate_working_hours (line 118) | def test_calculate_working_hours(self):
    method test_fetch_shift (line 164) | def test_fetch_shift(self):
    method test_fetch_shift_for_assignment_with_end_date (line 193) | def test_fetch_shift_for_assignment_with_end_date(self):
    method test_shift_start_and_end_timings (line 218) | def test_shift_start_and_end_timings(self):
    method test_fetch_shift_based_on_default_shift (line 235) | def test_fetch_shift_based_on_default_shift(self):
    method test_fetch_night_shift_for_assignment_without_end_date (line 250) | def test_fetch_night_shift_for_assignment_without_end_date(self):
    method test_fetch_night_shift_on_assignment_boundary (line 273) | def test_fetch_night_shift_on_assignment_boundary(self):
    method test_night_shift_not_fetched_outside_assignment_boundary_for_diff_start_date (line 298) | def test_night_shift_not_fetched_outside_assignment_boundary_for_diff_...
    method test_night_shift_not_fetched_outside_assignment_boundary_for_diff_end_date (line 320) | def test_night_shift_not_fetched_outside_assignment_boundary_for_diff_...
    method test_night_shift_not_fetched_outside_before_shift_margin (line 342) | def test_night_shift_not_fetched_outside_before_shift_margin(self):
    method test_night_shift_not_fetched_outside_after_shift_margin (line 364) | def test_night_shift_not_fetched_outside_after_shift_margin(self):
    method test_fetch_night_shift_in_margin_period_after_shift (line 390) | def test_fetch_night_shift_in_margin_period_after_shift(self):
    method test_fetch_night_shift_in_margin_period_before_shift (line 423) | def test_fetch_night_shift_in_margin_period_before_shift(self):
    method test_consecutive_shift_assignments_overlapping_within_grace_period (line 456) | def test_consecutive_shift_assignments_overlapping_within_grace_period...
    method test_geofencing (line 492) | def test_geofencing(self):
    method test_bulk_fetch_shift (line 549) | def test_bulk_fetch_shift(self):
    method test_bulk_fetch_shift_if_shift_settings_change_for_the_same_shift (line 583) | def test_bulk_fetch_shift_if_shift_settings_change_for_the_same_shift(...
    method test_if_logs_are_marked_invalid (line 611) | def test_if_logs_are_marked_invalid(self):
    method test_if_logs_are_marked_valid_again (line 626) | def test_if_logs_are_marked_valid_again(self):
    method test_validate_time_change (line 642) | def test_validate_time_change(self):
    method test_modifying_half_attendance_created_from_leave (line 663) | def test_modifying_half_attendance_created_from_leave(self):
    method test_modifying_half_day_attendance_when_checkins_are_absent (line 714) | def test_modifying_half_day_attendance_when_checkins_are_absent(self):
    method test_half_day_attendance_when_checkins_exists_but_threshold_is_unmet (line 758) | def test_half_day_attendance_when_checkins_exists_but_threshold_is_unm...
    method test_half_day_attendance_when_employee_checkins_exists_and_attendance_is_full_day (line 814) | def test_half_day_attendance_when_employee_checkins_exists_and_attenda...
  function make_n_checkins (line 872) | def make_n_checkins(employee, n, hours_to_reverse=1):
  function make_checkin (line 879) | def make_checkin(employee, time=None, latitude=None, longitude=None, log...
  function make_shift_location (line 897) | def make_shift_location(location_name, latitude, longitude, checkin_radi...
  function create_leave_allocation (line 911) | def create_leave_allocation(employee, leave_type, from_date, to_date, ne...

FILE: hrms/hr/doctype/employee_feedback_criteria/employee_feedback_criteria.py
  class EmployeeFeedbackCriteria (line 8) | class EmployeeFeedbackCriteria(Document):

FILE: hrms/hr/doctype/employee_feedback_criteria/test_employee_feedback_criteria.py
  class TestEmployeeFeedbackCriteria (line 8) | class TestEmployeeFeedbackCriteria(HRMSTestSuite):

FILE: hrms/hr/doctype/employee_feedback_rating/employee_feedback_rating.py
  class EmployeeFeedbackRating (line 8) | class EmployeeFeedbackRating(Document):

FILE: hrms/hr/doctype/employee_grade/employee_grade.py
  class EmployeeGrade (line 8) | class EmployeeGrade(Document):

FILE: hrms/hr/doctype/employee_grade/employee_grade_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/employee_grade/test_employee_grade.py
  class TestEmployeeGrade (line 7) | class TestEmployeeGrade(HRMSTestSuite):

FILE: hrms/hr/doctype/employee_grievance/employee_grievance.py
  class EmployeeGrievance (line 9) | class EmployeeGrievance(Document):
    method on_submit (line 39) | def on_submit(self):
    method on_discard (line 47) | def on_discard(self):

FILE: hrms/hr/doctype/employee_grievance/test_employee_grievance.py
  class TestEmployeeGrievance (line 12) | class TestEmployeeGrievance(HRMSTestSuite):
    method test_create_employee_grievance (line 13) | def test_create_employee_grievance(self):
    method test_status_on_discard (line 26) | def test_status_on_discard(self):
  function create_employee_grievance (line 39) | def create_employee_grievance(raised_by, raised_against, grievance_type):
  function create_grievance_type (line 61) | def create_grievance_type():

FILE: hrms/hr/doctype/employee_health_insurance/employee_health_insurance.py
  class EmployeeHealthInsurance (line 8) | class EmployeeHealthInsurance(Document):

FILE: hrms/hr/doctype/employee_health_insurance/test_employee_health_insurance.py
  class TestEmployeeHealthInsurance (line 7) | class TestEmployeeHealthInsurance(HRMSTestSuite):

FILE: hrms/hr/doctype/employee_onboarding/employee_onboarding.js
  method mark_as_completed (line 113) | mark_as_completed(frm) {

FILE: hrms/hr/doctype/employee_onboarding/employee_onboarding.py
  class IncompleteTaskError (line 13) | class IncompleteTaskError(frappe.ValidationError):
  class EmployeeOnboarding (line 17) | class EmployeeOnboarding(EmployeeBoardingController):
    method validate (line 49) | def validate(self):
    method set_employee (line 54) | def set_employee(self):
    method validate_duplicate_employee_onboarding (line 58) | def validate_duplicate_employee_onboarding(self):
    method validate_employee_creation (line 69) | def validate_employee_creation(self):
    method on_submit (line 84) | def on_submit(self):
    method on_update_after_submit (line 87) | def on_update_after_submit(self):
    method on_cancel (line 90) | def on_cancel(self):
    method mark_onboarding_as_completed (line 94) | def mark_onboarding_as_completed(self):
  function make_employee (line 103) | def make_employee(source_name: str, target_doc: str | Document | None = ...

FILE: hrms/hr/doctype/employee_onboarding/test_employee_onboarding.py
  class TestEmployeeOnboarding (line 17) | class TestEmployeeOnboarding(HRMSTestSuite):
    method test_employee_onboarding_incomplete_task (line 18) | def test_employee_onboarding_incomplete_task(self):
    method test_mark_onboarding_as_completed (line 62) | def test_mark_onboarding_as_completed(self):
  function get_job_applicant (line 83) | def get_job_applicant():
  function get_job_offer (line 96) | def get_job_offer(applicant_name):
  function create_employee_onboarding (line 106) | def create_employee_onboarding():
  function get_task_dates (line 143) | def get_task_dates(task: str) -> tuple[str, str]:

FILE: hrms/hr/doctype/employee_onboarding_template/employee_onboarding_template.py
  class EmployeeOnboardingTemplate (line 8) | class EmployeeOnboardingTemplate(Document):

FILE: hrms/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py
  class TestEmployeeOnboardingTemplate (line 7) | class TestEmployeeOnboardingTemplate(HRMSTestSuite):

FILE: hrms/hr/doctype/employee_performance_feedback/employee_performance_feedback.js
  method onload (line 5) | onload(frm) {
  method refresh (line 9) | refresh(frm) {
  method employee (line 13) | employee(frm) {
  method appraisal (line 17) | appraisal(frm) {
  method set_filters (line 25) | set_filters(frm) {
  method set_reviewer (line 43) | set_reviewer(frm) {

FILE: hrms/hr/doctype/employee_performance_feedback/employee_performance_feedback.py
  class EmployeePerformanceFeedback (line 14) | class EmployeePerformanceFeedback(Document, AppraisalMixin):
    method validate (line 43) | def validate(self):
    method on_submit (line 51) | def on_submit(self):
    method on_cancel (line 54) | def on_cancel(self):
    method validate_employee (line 57) | def validate_employee(self):
    method validate_appraisal (line 68) | def validate_appraisal(self):
    method set_total_score (line 76) | def set_total_score(self):
    method update_avg_feedback_score_in_appraisal (line 84) | def update_avg_feedback_score_in_appraisal(self):
    method set_feedback_criteria (line 92) | def set_feedback_criteria(self):

FILE: hrms/hr/doctype/employee_performance_feedback/test_employee_performance_feedback.py
  class TestEmployeePerformanceFeedback (line 15) | class TestEmployeePerformanceFeedback(HRMSTestSuite):
    method setUp (line 16) | def setUp(self):
    method test_validate_employees (line 33) | def test_validate_employees(self):
    method test_set_feedback_criteria (line 46) | def test_set_feedback_criteria(self):
    method test_set_total_score (line 57) | def test_set_total_score(self):
    method test_update_avg_feedback_score_in_appraisal (line 74) | def test_update_avg_feedback_score_in_appraisal(self):
    method test_update_avg_feedback_score_on_cancel (line 106) | def test_update_avg_feedback_score_on_cancel(self):
  function create_performance_feedback (line 129) | def create_performance_feedback(employee, reviewer, appraisal):

FILE: hrms/hr/doctype/employee_promotion/employee_promotion.py
  class EmployeePromotion (line 13) | class EmployeePromotion(Document):
    method validate (line 38) | def validate(self):
    method before_submit (line 41) | def before_submit(self):
    method on_submit (line 48) | def on_submit(self):
    method on_cancel (line 57) | def on_cancel(self):

FILE: hrms/hr/doctype/employee_promotion/test_employee_promotion.py
  class TestEmployeePromotion (line 11) | class TestEmployeePromotion(HRMSTestSuite):
    method test_submit_before_promotion_date (line 12) | def test_submit_before_promotion_date(self):
    method test_employee_history (line 35) | def test_employee_history(self):

FILE: hrms/hr/doctype/employee_property_history/employee_property_history.py
  class EmployeePropertyHistory (line 8) | class EmployeePropertyHistory(Document):

FILE: hrms/hr/doctype/employee_referral/employee_referral.py
  class EmployeeReferral (line 13) | class EmployeeReferral(Document):
    method validate (line 44) | def validate(self):
    method validate_unique_referral (line 51) | def validate_unique_referral(self):
    method set_full_name (line 62) | def set_full_name(self):
    method set_status (line 65) | def set_status(self):
    method set_referral_bonus_payment_status (line 68) | def set_referral_bonus_payment_status(self):
    method on_discard (line 75) | def on_discard(self):
  function create_job_applicant (line 80) | def create_job_applicant(source_name: str, target_doc: str | Document | ...
  function create_additional_salary (line 113) | def create_additional_salary(employee_referral: str) -> Document:

FILE: hrms/hr/doctype/employee_referral/employee_referral_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/employee_referral/test_employee_referral.py
  class TestEmployeeReferral (line 17) | class TestEmployeeReferral(HRMSTestSuite):
    method test_workflow_and_status_sync (line 18) | def test_workflow_and_status_sync(self):
    method test_status_on_discard (line 49) | def test_status_on_discard(self):
    method test_unique_referral (line 55) | def test_unique_referral(self):
  function create_employee_referral (line 63) | def create_employee_referral(email=None, do_not_submit=False):

FILE: hrms/hr/doctype/employee_separation/employee_separation.py
  class EmployeeSeparation (line 8) | class EmployeeSeparation(EmployeeBoardingController):
    method validate (line 38) | def validate(self):
    method on_submit (line 41) | def on_submit(self):
    method on_update_after_submit (line 44) | def on_update_after_submit(self):
    method on_cancel (line 47) | def on_cancel(self):

FILE: hrms/hr/doctype/employee_separation/test_employee_separation.py
  class TestEmployeeSeparation (line 10) | class TestEmployeeSeparation(HRMSTestSuite):
    method test_employee_separation (line 11) | def test_employee_separation(self):
  function create_employee_separation (line 29) | def create_employee_separation():

FILE: hrms/hr/doctype/employee_separation_template/employee_separation_template.py
  class EmployeeSeparationTemplate (line 8) | class EmployeeSeparationTemplate(Document):

FILE: hrms/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/employee_separation_template/test_employee_separation_template.py
  class TestEmployeeSeparationTemplate (line 7) | class TestEmployeeSeparationTemplate(HRMSTestSuite):

FILE: hrms/hr/doctype/employee_skill/employee_skill.py
  class EmployeeSkill (line 9) | class EmployeeSkill(Document):

FILE: hrms/hr/doctype/employee_skill_map/employee_skill_map.py
  class EmployeeSkillMap (line 9) | class EmployeeSkillMap(Document):

FILE: hrms/hr/doctype/employee_training/employee_training.py
  class EmployeeTraining (line 9) | class EmployeeTraining(Document):

FILE: hrms/hr/doctype/employee_transfer/employee_transfer.py
  class EmployeeTransfer (line 13) | class EmployeeTransfer(Document):
    method before_submit (line 39) | def before_submit(self):
    method on_submit (line 46) | def on_submit(self):
    method on_cancel (line 75) | def on_cancel(self):
    method validate_user_in_details (line 95) | def validate_user_in_details(self):

FILE: hrms/hr/doctype/employee_transfer/test_employee_transfer.py
  class TestEmployeeTransfer (line 12) | class TestEmployeeTransfer(HRMSTestSuite):
    method setUp (line 13) | def setUp(self):
    method test_submit_before_transfer_date (line 16) | def test_submit_before_transfer_date(self):
    method test_new_employee_creation (line 41) | def test_new_employee_creation(self):
    method test_employee_history (line 65) | def test_employee_history(self):
    method test_data_formatting_in_history (line 100) | def test_data_formatting_in_history(self):
  function create_company (line 110) | def create_company():
  function create_employee_transfer (line 122) | def create_employee_transfer(employee):

FILE: hrms/hr/doctype/employment_type/employment_type.py
  class EmploymentType (line 8) | class EmploymentType(Document):

FILE: hrms/hr/doctype/exit_interview/exit_interview.py
  class ExitInterview (line 12) | class ExitInterview(Document):
    method validate (line 44) | def validate(self):
    method validate_relieving_date (line 49) | def validate_relieving_date(self):
    method validate_duplicate_interview (line 58) | def validate_duplicate_interview(self):
    method set_employee_email (line 70) | def set_employee_email(self):
    method on_submit (line 74) | def on_submit(self):
    method on_cancel (line 80) | def on_cancel(self):
    method on_discard (line 84) | def on_discard(self):
    method update_interview_date_in_employee (line 87) | def update_interview_date_in_employee(self):
  function send_exit_questionnaire (line 95) | def send_exit_questionnaire(interviews: str | list) -> None:
  function get_interviews (line 132) | def get_interviews(interviews):
  function validate_questionnaire_settings (line 144) | def validate_questionnaire_settings():
  function show_email_summary (line 163) | def show_email_summary(email_success, email_failure):

FILE: hrms/hr/doctype/exit_interview/test_exit_interview.py
  class TestExitInterview (line 18) | class TestExitInterview(HRMSTestSuite):
    method test_duplicate_interview (line 19) | def test_duplicate_interview(self):
    method test_relieving_date_validation (line 27) | def test_relieving_date_validation(self):
    method test_interview_date_updated_in_employee_master (line 40) | def test_interview_date_updated_in_employee_master(self):
    method test_send_exit_questionnaire (line 57) | def test_send_exit_questionnaire(self):
    method test_status_on_discard (line 80) | def test_status_on_discard(self):
  function create_exit_interview (line 89) | def create_exit_interview(employee, save=True):
  function create_notification_template (line 109) | def create_notification_template():

FILE: hrms/hr/doctype/expected_skill_set/expected_skill_set.py
  class ExpectedSkillSet (line 9) | class ExpectedSkillSet(Document):

FILE: hrms/hr/doctype/expense_claim/expense_claim.js
  function set_in_company_currency (line 644) | async function set_in_company_currency(frm, doc, fields, exchange_rate =...

FILE: hrms/hr/doctype/expense_claim/expense_claim.py
  class InvalidExpenseApproverError (line 31) | class InvalidExpenseApproverError(frappe.ValidationError):
  class ExpenseApproverIdentityError (line 35) | class ExpenseApproverIdentityError(frappe.ValidationError):
  class MismatchError (line 39) | class MismatchError(frappe.ValidationError):
  class ExpenseClaim (line 43) | class ExpenseClaim(AccountsController, PWANotificationsMixin):
    method onload (line 97) | def onload(self):
    method after_insert (line 106) | def after_insert(self):
    method validate (line 109) | def validate(self):
    method set_status (line 123) | def set_status(self, update=False):
    method validate_company_and_department (line 156) | def validate_company_and_department(self):
    method validate_for_self_approval (line 165) | def validate_for_self_approval(self):
    method on_update (line 177) | def on_update(self):
    method after_delete (line 182) | def after_delete(self):
    method on_discard (line 185) | def on_discard(self):
    method before_submit (line 189) | def before_submit(self):
    method publish_update (line 195) | def publish_update(self):
    method on_submit (line 200) | def on_submit(self):
    method on_update_after_submit (line 212) | def on_update_after_submit(self):
    method on_cancel (line 217) | def on_cancel(self):
    method update_claimed_amount_in_employee_advance (line 234) | def update_claimed_amount_in_employee_advance(self):
    method update_task_and_project (line 238) | def update_task_and_project(self):
    method make_gl_entries (line 257) | def make_gl_entries(self, cancel=False):
    method get_gl_entries (line 262) | def get_gl_entries(self):
    method add_tax_gl_entries (line 384) | def add_tax_gl_entries(self, gl_entries):
    method set_default_accounting_dimension (line 406) | def set_default_accounting_dimension(self):
    method create_exchange_gain_loss_je (line 426) | def create_exchange_gain_loss_je(self):
    method validate_account_details (line 482) | def validate_account_details(self):
    method calculate_total_amount (line 495) | def calculate_total_amount(self):
    method set_base_fields_amount (line 511) | def set_base_fields_amount(self, doc, fields, exchange_rate=None):
    method calculate_taxes (line 522) | def calculate_taxes(self):
    method validate_advances (line 547) | def validate_advances(self):
    method validate_sanctioned_amount (line 577) | def validate_sanctioned_amount(self):
    method set_expense_account (line 584) | def set_expense_account(self, validate=False):
    method update_against_claim_in_pe (line 591) | def update_against_claim_in_pe(self):
  function update_reimbursed_amount (line 621) | def update_reimbursed_amount(doc):
  function get_total_reimbursed_amount (line 630) | def get_total_reimbursed_amount(doc):
  function get_outstanding_amount_for_claim (line 657) | def get_outstanding_amount_for_claim(claim):
  function make_bank_entry (line 684) | def make_bank_entry(dt: str, dn: str) -> dict:
  function get_expense_claim_account_and_cost_center (line 728) | def get_expense_claim_account_and_cost_center(expense_claim_type: str, c...
  function get_expense_claim_account (line 736) | def get_expense_claim_account(expense_claim_type: str, company: str) -> ...
  function get_advances (line 752) | def get_advances(expense_claim: str | dict | Document, advance_id: str |...
  function get_expense_claim (line 794) | def get_expense_claim(employee_advance: str | dict, payment_via_journal_...
  function get_expense_claim_advances (line 826) | def get_expense_claim_advances(expense_claim, employee_advance):
  function update_payment_for_expense_claim (line 917) | def update_payment_for_expense_claim(doc, method=None):
  function update_outstanding_amount_in_payment_entry (line 942) | def update_outstanding_amount_in_payment_entry(expense_claim: dict, pe_r...
  function validate_expense_claim_in_jv (line 949) | def validate_expense_claim_in_jv(doc, method=None):
  function make_expense_claim_for_delivery_trip (line 966) | def make_expense_claim_for_delivery_trip(
  function get_allocation_amount (line 980) | def get_allocation_amount(

FILE: hrms/hr/doctype/expense_claim/expense_claim_dashboard.py
  function get_data (line 4) | def get_data():

FILE: hrms/hr/doctype/expense_claim/test_expense_claim.py
  class TestExpenseClaim (line 24) | class TestExpenseClaim(HRMSTestSuite):
    method setUp (line 25) | def setUp(self):
    method test_total_expense_claim_for_project (line 42) | def test_total_expense_claim_for_project(self):
    method test_expense_claim_status_as_payment_from_journal_entry (line 72) | def test_expense_claim_status_as_payment_from_journal_entry(self):
    method test_expense_claim_status_as_payment_from_payment_entry (line 95) | def test_expense_claim_status_as_payment_from_payment_entry(self):
    method test_expense_claim_status_as_payment_allocation_using_pr (line 109) | def test_expense_claim_status_as_payment_allocation_using_pr(self):
    method test_expense_claim_against_fully_paid_advances (line 164) | def test_expense_claim_against_fully_paid_advances(self):
    method test_advance_amount_allocation_against_claim_with_taxes (line 190) | def test_advance_amount_allocation_against_claim_with_taxes(self):
    method test_expense_claim_partially_paid_via_advance (line 224) | def test_expense_claim_partially_paid_via_advance(self):
    method test_expense_claim_with_deducted_returned_advance (line 257) | def test_expense_claim_with_deducted_returned_advance(self):
    method test_expense_claim_gl_entry (line 315) | def test_expense_claim_gl_entry(self):
    method test_invalid_gain_loss_for_expense_claim (line 353) | def test_invalid_gain_loss_for_expense_claim(self):
    method test_rejected_expense_claim (line 388) | def test_rejected_expense_claim(self):
    method test_expense_approver_perms (line 403) | def test_expense_approver_perms(self):
    method test_multiple_payment_entries_against_expense (line 432) | def test_multiple_payment_entries_against_expense(self):
    method test_expense_claim_against_delivery_trip (line 472) | def test_expense_claim_against_delivery_trip(self):
    method test_journal_entry_against_expense_claim (line 490) | def test_journal_entry_against_expense_claim(self):
    method test_accounting_dimension_mapping (line 508) | def test_accounting_dimension_mapping(self):
    method test_rounding (line 538) | def test_rounding(self):
    method test_repost (line 563) | def test_repost(self):
    method test_company_department_validation (line 618) | def test_company_department_validation(self):
    method test_self_expense_approval (line 625) | def test_self_expense_approval(self):
    method test_self_expense_approval_not_allowed (line 653) | def test_self_expense_approval_not_allowed(self):
    method test_multicurrency_claim (line 697) | def test_multicurrency_claim(self):
    method test_advance_claim_multicurrency_gain_loss (line 807) | def test_advance_claim_multicurrency_gain_loss(self):
    method test_expense_claim_status_as_payment_after_unreconciliation (line 874) | def test_expense_claim_status_as_payment_after_unreconciliation(self):
    method test_status_on_discard (line 917) | def test_status_on_discard(self):
  function get_payable_account (line 930) | def get_payable_account(company):
  function generate_taxes (line 934) | def generate_taxes(company=None, rate=None) -> dict:
  function make_expense_claim (line 960) | def make_expense_claim(
  function make_claim_payment_entry (line 1017) | def make_claim_payment_entry(expense_claim, amount):
  function make_journal_entry (line 1031) | def make_journal_entry(expense_claim, do_not_submit=False):
  function create_payment_reconciliation (line 1044) | def create_payment_reconciliation(company, employee, payable_account):
  function allocate_using_payment_reconciliation (line 1054) | def allocate_using_payment_reconciliation(expense_claim, employee, journ...
  function create_project (line 1064) | def create_project(project_name, **args):

FILE: hrms/hr/doctype/expense_claim_account/expense_claim_account.py
  class ExpenseClaimAccount (line 8) | class ExpenseClaimAccount(Document):

FILE: hrms/hr/doctype/expense_claim_advance/expense_claim_advance.py
  class ExpenseClaimAdvance (line 8) | class ExpenseClaimAdvance(Document):

FILE: hrms/hr/doctype/expense_claim_detail/expense_claim_detail.py
  class ExpenseClaimDetail (line 8) | class ExpenseClaimDetail(Document):

FILE: hrms/hr/doctype/expense_claim_type/expense_claim_type.py
  class ExpenseClaimType (line 10) | class ExpenseClaimType(Document):
    method validate (line 27) | def validate(self):
    method validate_repeating_companies (line 31) | def validate_repeating_companies(self):
    method validate_accounts (line 40) | def validate_accounts(self):

FILE: hrms/hr/doctype/expense_claim_type/test_expense_claim_type.py
  class TestExpenseClaimType (line 9) | class TestExpenseClaimType(HRMSTestSuite):

FILE: hrms/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py
  class ExpenseTaxesandCharges (line 9) | class ExpenseTaxesandCharges(Document):

FILE: hrms/hr/doctype/full_and_final_asset/full_and_final_asset.py
  class FullandFinalAsset (line 8) | class FullandFinalAsset(Document):

FILE: hrms/hr/doctype/full_and_final_asset/test_full_and_final_asset.py
  class TestFullandFinalAsset (line 8) | class TestFullandFinalAsset(HRMSTestSuite):

FILE: hrms/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py
  class FullandFinalOutstandingStatement (line 8) | class FullandFinalOutstandingStatement(Document):

FILE: hrms/hr/doctype/full_and_final_statement/full_and_final_statement.py
  class FullandFinalStatement (line 15) | class FullandFinalStatement(Document):
    method before_insert (line 47) | def before_insert(self):
    method validate (line 51) | def validate(self):
    method before_submit (line 57) | def before_submit(self):
    method on_submit (line 62) | def on_submit(self):
    method on_cancel (line 65) | def on_cancel(self):
    method on_discard (line 69) | def on_discard(self):
    method validate_relieving_date (line 72) | def validate_relieving_date(self):
    method validate_settlement (line 82) | def validate_settlement(self, component_type):
    method validate_assets (line 90) | def validate_assets(self):
    method get_outstanding_statements (line 107) | def get_outstanding_statements(self):
    method get_assets_statements (line 122) | def get_assets_statements(self):
    method set_total_asset_recovery_cost (line 127) | def set_total_asset_recovery_cost(self):
    method set_totals (line 139) | def set_totals(self):
    method add_withheld_salary_slips (line 149) | def add_withheld_salary_slips(self):
    method create_component_row (line 173) | def create_component_row(self, components, component_type):
    method get_payable_component (line 184) | def get_payable_component(self):
    method get_receivable_component (line 192) | def get_receivable_component(self):
    method get_assets_movement (line 198) | def get_assets_movement(self):
    method create_journal_entry (line 236) | def create_journal_entry(self):
    method update_reference_document_payment_status (line 295) | def update_reference_document_payment_status(self, payable):
    method update_linked_payable_documents (line 301) | def update_linked_payable_documents(self):
  function get_account_and_amount (line 309) | def get_account_and_amount(ref_doctype: str, ref_document: str, company:...
  function update_full_and_final_statement_status (line 365) | def update_full_and_final_statement_status(doc, method=None):

FILE: hrms/hr/doctype/full_and_final_statement/full_and_final_statement_loan_utils.py
  function process_loan_accrual (line 16) | def process_loan_accrual(doc: "FullandFinalStatement"):
  function cancel_loan_repayment (line 80) | def cancel_loan_repayment(doc: "FullandFinalStatement"):

FILE: hrms/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
  class TestFullandFinalStatement (line 13) | class TestFullandFinalStatement(HRMSTestSuite):
    method setUp (line 14) | def setUp(self):
    method setup_fnf (line 17) | def setup_fnf(self):
    method test_check_bootstraped_data_asset_movement_and_jv_creation (line 24) | def test_check_bootstraped_data_asset_movement_and_jv_creation(self):
    method test_asset_cost (line 43) | def test_asset_cost(self):
    method test_journal_entry (line 54) | def test_journal_entry(self):
    method test_status_on_discard (line 69) | def test_status_on_discard(self):
  function create_full_and_final_statement (line 75) | def create_full_and_final_statement(employee):
  function create_asset_movement (line 83) | def create_asset_movement(employee):
  function create_asset (line 97) | def create_asset():

FILE: hrms/hr/doctype/goal/goal.js
  method refresh (line 5) | refresh(frm) {
  method set_filters (line 18) | set_filters(frm) {
  method add_custom_buttons (line 49) | add_custom_buttons(frm) {
  method kra (line 98) | kra(frm) {
  method is_group (line 129) | is_group(frm) {

FILE: hrms/hr/doctype/goal/goal.py
  class Goal (line 16) | class Goal(NestedSet):
    method before_insert (line 46) | def before_insert(self):
    method validate (line 50) | def validate(self):
    method on_update (line 60) | def on_update(self):
    method on_trash (line 75) | def on_trash(self):
    method after_delete (line 78) | def after_delete(self):
    method validate_parent_fields (line 82) | def validate_parent_fields(self):
    method validate_progress (line 106) | def validate_progress(self):
    method set_status (line 110) | def set_status(self, status=None):
    method update_kra_in_child_goals (line 120) | def update_kra_in_child_goals(self, doc_before_save):
    method update_parent_progress (line 128) | def update_parent_progress(self, old_parent=None):
    method update_goal_progress_in_appraisal (line 152) | def update_goal_progress_in_appraisal(self):
  function get_children (line 165) | def get_children(doctype: str, parent: str, is_root: bool = False, **fil...
  function _update_goal_completion_status (line 213) | def _update_goal_completion_status(goals: list[dict]) -> list[dict]:
  function update_progress (line 227) | def update_progress(progress: float, goal: str) -> None:
  function update_status (line 237) | def update_status(status: str, goals: str | list) -> None:
  function add_tree_node (line 255) | def add_tree_node():

FILE: hrms/hr/doctype/goal/goal_list.js
  method end_date (line 16) | end_date(value, df, doc) {

FILE: hrms/hr/doctype/goal/goal_tree.js
  method get_query (line 18) | get_query() {
  method default (line 63) | default() {
  method get_query (line 107) | get_query() {
  method default (line 118) | default() {
  method get_query (line 137) | get_query() {
  method default (line 146) | default() {
  method onload (line 163) | onload(treeview) {
  method onrender (line 175) | onrender(node) {
  method post_render (line 217) | post_render(treeview) {
  method get_label (line 221) | get_label(node) {
  function update_progress (line 271) | function update_progress(node, progress) {

FILE: hrms/hr/doctype/goal/test_goal.py
  class TestGoal (line 13) | class TestGoal(HRMSTestSuite):
    method setUp (line 14) | def setUp(self):
    method test_validate_parent_fields (line 20) | def test_validate_parent_fields(self):
    method test_set_status (line 36) | def test_set_status(self):
    method test_update_parent_progress (line 48) | def test_update_parent_progress(self):
    method test_update_parent_progress_on_goal_deletion (line 63) | def test_update_parent_progress_on_goal_deletion(self):
    method test_update_parent_progress_with_nested_goals (line 77) | def test_update_parent_progress_with_nested_goals(self):
    method test_update_old_parent_progress (line 103) | def test_update_old_parent_progress(self):
    method test_update_kra_in_child_goals (line 164) | def test_update_kra_in_child_goals(self):
    method test_update_status (line 179) | def test_update_status(self):
  function create_goal (line 211) | def create_goal(

FILE: hrms/hr/doctype/grievance_type/grievance_type.py
  class GrievanceType (line 8) | class GrievanceType(Document):

FILE: hrms/hr/doctype/grievance_type/test_grievance_type.py
  class TestGrievanceType (line 8) | class TestGrievanceType(HRMSTestSuite):

FILE: hrms/hr/doctype/holiday_list_assignment/holiday_list_assignment.py
  class HolidayListAssignment (line 12) | class HolidayListAssignment(Document):
    method holiday_list_start (line 32) | def holiday_list_start(self):
    method holiday_list_end (line 36) | def holiday_list_end(self):
    method validate (line 39) | def validate(self):
    method validate_existing_assignment (line 43) | def validate_existing_assignment(self):
    method validate_assignment_start_date (line 60) | def validate_assignment_start_date(self):

FILE: hrms/hr/doctype/holiday_list_assignment/test_holiday_list_assignment.py
  class IntegrationTestHolidayListAssignment (line 17) | class IntegrationTestHolidayListAssignment(HRMSTestSuite):
    method setUp (line 23) | def setUp(self):
    method test_exisitng_assignment (line 32) | def test_exisitng_assignment(self):
    method test_fetch_correct_holiday_list_assignment (line 49) | def test_fetch_correct_holiday_list_assignment(self):
    method test_default_to_company_holiday_list_assignment (line 71) | def test_default_to_company_holiday_list_assignment(self):
  function create_holiday_list_assignment (line 78) | def create_holiday_list_assignment(
  function assign_holiday_list (line 111) | def assign_holiday_list(holiday_list, company_name):

FILE: hrms/hr/doctype/hr_settings/hr_settings.py
  class HRSettings (line 14) | class HRSettings(Document):
    method validate (line 59) | def validate(self):
    method set_naming_series (line 68) | def set_naming_series(self):
    method validate_frequency_change (line 78) | def validate_frequency_change(self):
    method freq_changed_from_weekly_to_monthly (line 105) | def freq_changed_from_weekly_to_monthly(self):
    method freq_changed_from_monthly_to_weekly (line 108) | def freq_changed_from_monthly_to_weekly(self):
    method show_freq_change_warning (line 111) | def show_freq_change_warning(self, from_date, to_date):
  function set_proceed_with_frequency_change (line 138) | def set_proceed_with_frequency_change():

FILE: hrms/hr/doctype/hr_settings/test_hr_settings.py
  class TestHRSettings (line 7) | class TestHRSettings(HRMSTestSuite):

FILE: hrms/hr/doctype/identification_document_type/identification_document_type.py
  class IdentificationDocumentType (line 8) | class IdentificationDocumentType(Document):

FILE: hrms/hr/doctype/identification_document_type/test_identification_document_type.py
  class TestIdentificationDocumentType (line 7) | class TestIdentificationDocumentType(HRMSTestSuite):

FILE: hrms/hr/doctype/interest/interest.py
  class Interest (line 8) | class Interest(Document):

FILE: hrms/hr/doctype/interest/test_interest.py
  class TestInterest (line 9) | class TestInterest(HRMSTestSuite):

FILE: hrms/hr/doctype/interview/interview.js
  method primary_action (line 111) | primary_action(values) {
  method set_applicable_interviewers (line 217) | set_applicable_interviewers(frm) {
  method load_skills_average_rating (line 233) | load_skills_average_rating(frm) {
  method load_feedback (line 244) | load_feedback(frm) {
  method render_feedback (line 257) | render_feedback(frm) {
  method calculate_reviews_per_rating (line 271) | calculate_reviews_per_rating(frm) {

FILE: hrms/hr/doctype/interview/interview.py
  class DuplicateInterviewRoundError (line 14) | class DuplicateInterviewRoundError(frappe.ValidationError):
  class Interview (line 18) | class Interview(Document):
    method validate (line 46) | def validate(self):
    method on_submit (line 50) | def on_submit(self):
    method validate_duplicate_interview (line 58) | def validate_duplicate_interview(self):
    method validate_designation (line 74) | def validate_designation(self):
    method show_job_applicant_update_dialog (line 87) | def show_job_applicant_update_dialog(self):
    method get_job_applicant_status (line 106) | def get_job_applicant_status(self) -> str | None:
    method reschedule_interview (line 111) | def reschedule_interview(
    method on_discard (line 153) | def on_discard(self):
  function get_interviewers (line 158) | def get_interviewers(interview_round: str) -> list[str]:
  function get_recipients (line 162) | def get_recipients(name, for_feedback=0):
  function get_feedback (line 179) | def get_feedback(interview: str) -> list[dict]:
  function get_skill_wise_average_rating (line 202) | def get_skill_wise_average_rating(interview: str) -> list[dict]:
  function update_job_applicant_status (line 220) | def update_job_applicant_status(status: str, job_applicant: str):
  function send_interview_reminder (line 243) | def send_interview_reminder():
  function send_daily_feedback_reminder (line 290) | def send_daily_feedback_reminder():
  function get_expected_skill_set (line 340) | def get_expected_skill_set(interview_round: str) -> list[dict]:
  function create_interview_feedback (line 347) | def create_interview_feedback(data: str | dict, interview_name: str, int...
  function get_interviewer_list (line 380) | def get_interviewer_list(
  function get_events (line 403) | def get_events(start: str, end: str, filters: str | None = None):

FILE: hrms/hr/doctype/interview/test_interview.py
  class TestInterview (line 26) | class TestInterview(HRMSTestSuite):
    method test_validations_for_designation (line 27) | def test_validations_for_designation(self):
    method test_notification_on_rescheduling (line 34) | def test_notification_on_rescheduling(self):
    method test_notification_for_scheduling (line 60) | def test_notification_for_scheduling(self):
    method test_notification_for_feedback_submission (line 83) | def test_notification_for_feedback_submission(self):
    method test_get_interview_details_for_applicant_dashboard (line 104) | def test_get_interview_details_for_applicant_dashboard(self):
    method test_skill_wise_average_rating (line 121) | def test_skill_wise_average_rating(self):
    method test_get_feedback (line 141) | def test_get_feedback(self):
    method test_job_applicant_status_update_on_interview_submit (line 190) | def test_job_applicant_status_update_on_interview_submit(self):
    method test_status_on_discard (line 199) | def test_status_on_discard(self):
  function create_interview_and_dependencies (line 209) | def create_interview_and_dependencies(
  function create_interview_round (line 250) | def create_interview_round(name, skill_set, interviewers=None, designati...
  function create_skill_set (line 272) | def create_skill_set(skill_set):
  function create_interview_type (line 280) | def create_interview_type(name="test_interview_type"):
  function setup_reminder_settings (line 292) | def setup_reminder_settings():

FILE: hrms/hr/doctype/interview_detail/interview_detail.py
  class InterviewDetail (line 8) | class InterviewDetail(Document):

FILE: hrms/hr/doctype/interview_feedback/interview_feedback.py
  class InterviewFeedback (line 12) | class InterviewFeedback(Document):
    method validate (line 34) | def validate(self):
    method on_submit (line 40) | def on_submit(self):
    method on_cancel (line 43) | def on_cancel(self):
    method validate_interviewer (line 46) | def validate_interviewer(self):
    method validate_interview_date (line 55) | def validate_interview_date(self):
    method validate_duplicate (line 65) | def validate_duplicate(self):
    method calculate_average_rating (line 78) | def calculate_average_rating(self):
    method update_interview_average_rating (line 88) | def update_interview_average_rating(self):
  function get_applicable_interviewers (line 104) | def get_applicable_interviewers(interview: str) -> list[str]:

FILE: hrms/hr/doctype/interview_feedback/test_interview_feedback.py
  class TestInterviewFeedback (line 15) | class TestInterviewFeedback(HRMSTestSuite):
    method test_validation_for_skill_set (line 16) | def test_validation_for_skill_set(self):
    method test_average_ratings_on_feedback_submission_and_cancellation (line 35) | def test_average_ratings_on_feedback_submission_and_cancellation(self):
  function create_interview_feedback (line 73) | def create_interview_feedback(interview, interviewer, skills_ratings):
  function get_skills_rating (line 88) | def get_skills_rating(interview_round):

FILE: hrms/hr/doctype/interview_round/interview_round.py
  class InterviewRound (line 11) | class InterviewRound(Document):
  function create_interview (line 35) | def create_interview(interview_round: str) -> Document:

FILE: hrms/hr/doctype/interview_round/test_interview_round.py
  class TestInterviewRound (line 9) | class TestInterviewRound(HRMSTestSuite):

FILE: hrms/hr/doctype/interview_type/interview_type.py
  class InterviewType (line 9) | class InterviewType(Document):

FILE: hrms/hr/doctype/interview_type/test_interview_type.py
  class TestInterviewType (line 8) | class TestInterviewType(HRMSTestSuite):

FILE: hrms/hr/doctype/interviewer/interviewer.py
  class Interviewer (line 9) | class Interviewer(Document):

FILE: hrms/hr/doctype/job_applicant/job_applicant.js
  method primary_action (line 96) | primary_action(values) {

FILE: hrms/hr/doctype/job_applicant/job_applicant.py
  class DuplicationError (line 16) | class DuplicationError(frappe.ValidationError):
  class JobApplicant (line 20) | class JobApplicant(Document):
    method onload (line 49) | def onload(self):
    method autoname (line 54) | def autoname(self):
    method validate (line 61) | def validate(self):
    method before_insert (line 72) | def before_insert(self):
    method set_status_for_employee_referral (line 80) | def set_status_for_employee_referral(self):
  function create_interview (line 89) | def create_interview(job_applicant: str, interview_round: str) -> Document:
  function get_interview_details (line 116) | def get_interview_details(job_applicant: str) -> dict:
  function get_applicant_to_hire_percentage (line 135) | def get_applicant_to_hire_percentage() -> dict:

FILE: hrms/hr/doctype/job_applicant/job_applicant_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/job_applicant/test_job_applicant.py
  class TestJobApplicant (line 14) | class TestJobApplicant(HRMSTestSuite):
    method test_job_applicant_naming (line 15) | def test_job_applicant_naming(self):
    method test_update_applicant_to_employee (line 36) | def test_update_applicant_to_employee(self):

FILE: hrms/hr/doctype/job_applicant_source/job_applicant_source.py
  class JobApplicantSource (line 8) | class JobApplicantSource(Document):

FILE: hrms/hr/doctype/job_applicant_source/test_job_applicant_source.py
  class TestJobApplicantSource (line 7) | class TestJobApplicantSource(HRMSTestSuite):

FILE: hrms/hr/doctype/job_offer/job_offer.py
  class JobOffer (line 12) | class JobOffer(Document):
    method onload (line 39) | def onload(self):
    method validate (line 43) | def validate(self):
    method validate_vacancies (line 55) | def validate_vacancies(self):
    method on_change (line 67) | def on_change(self):
    method get_job_offer (line 70) | def get_job_offer(self, from_date, to_date):
    method on_discard (line 83) | def on_discard(self):
  function update_job_applicant (line 87) | def update_job_applicant(status, job_applicant):
  function get_staffing_plan_detail (line 92) | def get_staffing_plan_detail(designation, company, offer_date):
  function make_employee (line 117) | def make_employee(source_name: str, target_doc: str | Document | None = ...
  function get_offer_acceptance_rate (line 139) | def get_offer_acceptance_rate(company: str | None = None, department: st...

FILE: hrms/hr/doctype/job_offer/test_job_offer.py
  class TestJobOffer (line 16) | class TestJobOffer(HRMSTestSuite):
    method setUp (line 17) | def setUp(self):
    method test_job_offer_creation_against_vacancies (line 20) | def test_job_offer_creation_against_vacancies(self):
    method test_job_applicant_update (line 39) | def test_job_applicant_update(self):
    method test_recruitment_metrics (line 55) | def test_recruitment_metrics(self):
    method test_status_on_save (line 70) | def test_status_on_save(self):
  function create_job_offer (line 78) | def create_job_offer(**args):
  function create_staffing_plan (line 100) | def create_staffing_plan(**args):

FILE: hrms/hr/doctype/job_offer_term/job_offer_term.py
  class JobOfferTerm (line 8) | class JobOfferTerm(Document):

FILE: hrms/hr/doctype/job_offer_term_template/job_offer_term_template.py
  class JobOfferTermTemplate (line 8) | class JobOfferTermTemplate(Document):

FILE: hrms/hr/doctype/job_offer_term_template/test_job_offer_term_template.py
  class TestJobOfferTermTemplate (line 8) | class TestJobOfferTermTemplate(HRMSTestSuite):

FILE: hrms/hr/doctype/job_opening/job_opening.py
  class JobOpening (line 19) | class JobOpening(WebsiteGenerator):
    method autoname (line 61) | def autoname(self):
    method validate (line 64) | def validate(self):
    method on_update (line 71) | def on_update(self):
    method update_closing_date (line 74) | def update_closing_date(self):
    method validate_dates (line 87) | def validate_dates(self):
    method validate_current_vacancies (line 93) | def validate_current_vacancies(self):
    method update_job_requisition_status (line 126) | def update_job_requisition_status(self):
    method get_context (line 135) | def get_context(self, context):
  function close_expired_job_openings (line 141) | def close_expired_job_openings():

FILE: hrms/hr/doctype/job_opening/job_opening_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/job_opening/test_job_opening.py
  class TestJobOpening (line 14) | class TestJobOpening(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_vacancies_fulfilled (line 18) | def test_vacancies_fulfilled(self):
    method test_close_expired_job_openings (line 52) | def test_close_expired_job_openings(self):
  function get_job_opening (line 72) | def get_job_opening(**args):

FILE: hrms/hr/doctype/job_opening_template/job_opening_template.py
  class JobOpeningTemplate (line 8) | class JobOpeningTemplate(Document):

FILE: hrms/hr/doctype/job_opening_template/test_job_opening_template.py
  class IntegrationTestJobOpeningTemplate (line 8) | class IntegrationTestJobOpeningTemplate(HRMSTestSuite):

FILE: hrms/hr/doctype/job_requisition/job_requisition.py
  class JobRequisition (line 11) | class JobRequisition(Document):
    method validate (line 39) | def validate(self):
    method validate_duplicates (line 43) | def validate_duplicates(self):
    method set_time_to_fill (line 65) | def set_time_to_fill(self):
    method associate_job_opening (line 70) | def associate_job_opening(self, job_opening: str) -> None:
  function make_job_opening (line 83) | def make_job_opening(source_name: str, target_doc: str | Document | None...
  function get_avg_time_to_fill (line 111) | def get_avg_time_to_fill(

FILE: hrms/hr/doctype/job_requisition/job_requisition_list.js
  method expected_by (line 15) | expected_by(value, df, doc) {

FILE: hrms/hr/doctype/job_requisition/test_job_requisition.py
  class TestJobRequisition (line 14) | class TestJobRequisition(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_make_job_opening (line 18) | def test_make_job_opening(self):
    method test_associate_job_opening (line 30) | def test_associate_job_opening(self):
    method test_time_to_fill (line 39) | def test_time_to_fill(self):
  function make_job_requisition (line 49) | def make_job_requisition(**args):

FILE: hrms/hr/doctype/kra/kra.py
  class KRA (line 8) | class KRA(Document):

FILE: hrms/hr/doctype/kra/test_kra.py
  class TestKRA (line 8) | class TestKRA(HRMSTestSuite):

FILE: hrms/hr/doctype/leave_adjustment/leave_adjustment.js
  method refresh (line 5) | refresh(frm) {
  method employee (line 17) | employee(frm) {
  method leave_type (line 22) | leave_type(frm) {
  method posting_date (line 28) | posting_date(frm) {

FILE: hrms/hr/doctype/leave_adjustment/leave_adjustment.py
  class LeaveAdjustment (line 14) | class LeaveAdjustment(Document):
    method before_validate (line 39) | def before_validate(self):
    method before_save (line 44) | def before_save(self):
    method set_leaves_after_adjustment (line 47) | def set_leaves_after_adjustment(self):
    method validate (line 53) | def validate(self):
    method validate_duplicate_leave_adjustment (line 59) | def validate_duplicate_leave_adjustment(self):
    method validate_non_zero_adjustment (line 72) | def validate_non_zero_adjustment(self):
    method validate_over_allocation (line 76) | def validate_over_allocation(self):
    method validate_leave_balance (line 91) | def validate_leave_balance(self):
    method on_submit (line 106) | def on_submit(self):
    method on_cancel (line 109) | def on_cancel(self):
    method create_leave_ledger_entry (line 112) | def create_leave_ledger_entry(self, submit):
  function get_leave_allocation_for_posting_date (line 127) | def get_leave_allocation_for_posting_date(
  function get_allocated_leave_types (line 148) | def get_allocated_leave_types(

FILE: hrms/hr/doctype/leave_adjustment/test_leave_adjustment.py
  class TestLeaveAdjustment (line 17) | class TestLeaveAdjustment(HRMSTestSuite):
    method setUp (line 18) | def setUp(self):
    method test_duplicate_leave_adjustment (line 30) | def test_duplicate_leave_adjustment(self):
    method test_adjustment_for_over_allocation (line 37) | def test_adjustment_for_over_allocation(self):
    method test_adjustment_for_negative_leave_balance (line 52) | def test_adjustment_for_negative_leave_balance(self):
    method test_increase_balance_with_adjustment (line 69) | def test_increase_balance_with_adjustment(self):
    method test_decrease_balance_with_adjustment (line 80) | def test_decrease_balance_with_adjustment(self):
    method test_decrease_balance_after_leave_is_applied (line 87) | def test_decrease_balance_after_leave_is_applied(self):
    method test_precision (line 110) | def test_precision(self):
    method test_back_dated_leave_adjustment (line 118) | def test_back_dated_leave_adjustment(self):
    method test_reduction_type_adjustment_while_carry_forwarding_leaves (line 152) | def test_reduction_type_adjustment_while_carry_forwarding_leaves(self):
    method test_allocate_type_adjustment_while_carry_forwarding_leaves (line 189) | def test_allocate_type_adjustment_while_carry_forwarding_leaves(self):
  function create_leave_adjustment (line 227) | def create_leave_adjustment(leave_allocation, adjustment_type, leaves_to...

FILE: hrms/hr/doctype/leave_allocation/leave_allocation.js
  method primary_action (line 83) | primary_action(values) {

FILE: hrms/hr/doctype/leave_allocation/leave_allocation.py
  class OverlapError (line 20) | class OverlapError(frappe.ValidationError):
  class BackDatedAllocationError (line 24) | class BackDatedAllocationError(frappe.ValidationError):
  class OverAllocationError (line 28) | class OverAllocationError(frappe.ValidationError):
  class LessAllocationError (line 32) | class LessAllocationError(frappe.ValidationError):
  class ValueMultiplierError (line 36) | class ValueMultiplierError(frappe.ValidationError):
  class LeaveAllocation (line 40) | class LeaveAllocation(Document):
    method validate (line 75) | def validate(self):
    method validate_leave_days_and_dates (line 83) | def validate_leave_days_and_dates(self):
    method validate_leave_allocation_days (line 89) | def validate_leave_allocation_days(self):
    method on_submit (line 113) | def on_submit(self):
    method on_cancel (line 121) | def on_cancel(self):
    method on_update_after_submit (line 129) | def on_update_after_submit(self):
    method get_existing_leave_count (line 153) | def get_existing_leave_count(self):
    method validate_earned_leave_update (line 170) | def validate_earned_leave_update(self):
    method validate_against_leave_applications (line 183) | def validate_against_leave_applications(self):
    method update_leave_policy_assignments_when_no_allocations_left (line 202) | def update_leave_policy_assignments_when_no_allocations_left(self):
    method validate_period (line 212) | def validate_period(self):
    method validate_lwp (line 216) | def validate_lwp(self):
    method validate_allocation_overlap (line 222) | def validate_allocation_overlap(self):
    method validate_back_dated_allocation (line 248) | def validate_back_dated_allocation(self):
    method set_total_leaves_allocated (line 266) | def set_total_leaves_allocated(self):
    method limit_carry_forward_based_on_max_allowed_leaves (line 289) | def limit_carry_forward_based_on_max_allowed_leaves(self):
    method set_carry_forwarded_leaves_in_previous_allocation (line 295) | def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=...
    method validate_total_leaves_allocated (line 308) | def validate_total_leaves_allocated(self):
    method create_leave_ledger_entry (line 329) | def create_leave_ledger_entry(self, submit=True):
    method allocate_leaves_manually (line 354) | def allocate_leaves_manually(self, new_leaves: str | float, from_date:...
    method get_monthly_earned_leave (line 409) | def get_monthly_earned_leave(self):
    method create_leave_adjustment (line 433) | def create_leave_adjustment(
    method retry_failed_allocations (line 455) | def retry_failed_allocations(self, failed_allocations: list) -> None:
  function get_previous_allocation (line 514) | def get_previous_allocation(from_date, leave_type, employee):
  function get_leave_allocation_for_period (line 539) | def get_leave_allocation_for_period(employee, leave_type, from_date, to_...
  function get_carry_forwarded_leaves (line 560) | def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward...
  function get_unused_leaves (line 579) | def get_unused_leaves(employee, leave_type, from_date, to_date):
  function validate_carry_forward (line 595) | def validate_carry_forward(leave_type):
  function show_expire_leave_dialog (line 600) | def show_expire_leave_dialog(expired_leaves, leave_type):
  function expire_carried_forward_allocation (line 616) | def expire_carried_forward_allocation():

FILE: hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/leave_allocation/test_earned_leave_schedule.py
  class TestLeaveAllocation (line 12) | class TestLeaveAllocation(HRMSTestSuite):
    method setUp (line 13) | def setUp(self):
    method test_schedule_for_monthly_earned_leave_allocated_on_first_day (line 28) | def test_schedule_for_monthly_earned_leave_allocated_on_first_day(self):
    method test_schedule_for_monthly_earned_leave_allocated_on_last_day (line 51) | def test_schedule_for_monthly_earned_leave_allocated_on_last_day(self):
    method test_schedule_for_monthly_earned_leave_allocated_on_doj (line 74) | def test_schedule_for_monthly_earned_leave_allocated_on_doj(self):
    method test_schedule_for_quaterly_earned_leave_allocated_on_first_day (line 98) | def test_schedule_for_quaterly_earned_leave_allocated_on_first_day(self):
    method test_schedule_for_quaterly_earned_leave_allocated_on_last_day (line 121) | def test_schedule_for_quaterly_earned_leave_allocated_on_last_day(self):
    method test_schedule_for_half_yearly_earned_leave_allocated_on_first_day (line 144) | def test_schedule_for_half_yearly_earned_leave_allocated_on_first_day(...
    method test_schedule_for_half_yearly_earned_leave_allocated_on_last_day (line 168) | def test_schedule_for_half_yearly_earned_leave_allocated_on_last_day(s...
    method test_schedule_for_yearly_earned_leave_allocated_on_first_day (line 191) | def test_schedule_for_yearly_earned_leave_allocated_on_first_day(self):
    method test_schedule_for_yearly_earned_leave_allocated_on_last_day (line 214) | def test_schedule_for_yearly_earned_leave_allocated_on_last_day(self):
    method test_schedule_when_doj_is_in_the_middle_of_leave_period (line 237) | def test_schedule_when_doj_is_in_the_middle_of_leave_period(self):
    method test_schedule_when_assignment_is_based_on_doj (line 257) | def test_schedule_when_assignment_is_based_on_doj(self):
    method test_schedule_when_leave_policy_is_assigned_in_middle_of_the_period_allocated_on_first_day (line 277) | def test_schedule_when_leave_policy_is_assigned_in_middle_of_the_perio...
    method test_schedule_when_leave_policy_is_assigned_in_middle_of_the_period_allocated_on_last_day (line 295) | def test_schedule_when_leave_policy_is_assigned_in_middle_of_the_perio...
    method test_schedule_when_doj_is_end_of_big_month (line 313) | def test_schedule_when_doj_is_end_of_big_month(self):
    method test_absence_of_earned_leave_schedule_for_non_earned_leave_types (line 341) | def test_absence_of_earned_leave_schedule_for_non_earned_leave_types(s...
  function test_allocation_dates (line 368) | def test_allocation_dates(
  function create_earned_leave_schedule (line 401) | def create_earned_leave_schedule(
  function get_first_days_of_the_months (line 429) | def get_first_days_of_the_months(start_date, end_date):
  function get_last_days_of_the_months (line 434) | def get_last_days_of_the_months(start_date, end_date):
  function get_doj_for_months (line 443) | def get_doj_for_months(date_of_joining, start_date, end_date):
  function get_first_days_of_quarters (line 454) | def get_first_days_of_quarters(start_date, end_date):
  function get_last_days_of_quarters (line 459) | def get_last_days_of_quarters(start_date, end_date):
  function get_first_days_of_half_years (line 468) | def get_first_days_of_half_years(start_date, end_date):
  function get_last_days_of_half_years (line 473) | def get_last_days_of_half_years(start_date, end_date):
  function get_first_days_of_years (line 480) | def get_first_days_of_years(start_date, end_date):
  function get_last_days_of_years (line 485) | def get_last_days_of_years(start_date, end_date):

FILE: hrms/hr/doctype/leave_allocation/test_earned_leaves.py
  class TestLeaveAllocation (line 31) | class TestLeaveAllocation(HRMSTestSuite):
    method setUp (line 32) | def setUp(self):
    method test_earned_leave_allocation (line 52) | def test_earned_leave_allocation(self):
    method test_earned_leave_update_after_submission (line 62) | def test_earned_leave_update_after_submission(self):
    method test_alloc_based_on_leave_period (line 75) | def test_alloc_based_on_leave_period(self):
    method test_alloc_on_month_end_based_on_leave_period (line 86) | def test_alloc_on_month_end_based_on_leave_period(self):
    method test_alloc_based_on_leave_period_with_cf_leaves (line 98) | def test_alloc_based_on_leave_period_with_cf_leaves(self):
    method test_alloc_based_on_joining_date (line 130) | def test_alloc_based_on_joining_date(self):
    method test_alloc_on_doj_based_on_leave_period (line 153) | def test_alloc_on_doj_based_on_leave_period(self):
    method test_alloc_on_doj_based_on_joining_date (line 170) | def test_alloc_on_doj_based_on_joining_date(self):
    method test_earned_leaves_creation (line 193) | def test_earned_leaves_creation(self):
    method test_overallocation (line 216) | def test_overallocation(self):
    method test_over_allocation_during_assignment_creation (line 240) | def test_over_allocation_during_assignment_creation(self):
    method test_overallocation_with_carry_forwarding (line 260) | def test_overallocation_with_carry_forwarding(self):
    method test_allocate_on_first_day (line 300) | def test_allocate_on_first_day(self):
    method test_allocate_on_last_day (line 320) | def test_allocate_on_last_day(self):
    method test_allocate_on_date_of_joining (line 346) | def test_allocate_on_date_of_joining(self):
    method test_backdated_pro_rated_allocation (line 378) | def test_backdated_pro_rated_allocation(self):
    method test_no_pro_rated_leaves_allocated_before_effective_date (line 400) | def test_no_pro_rated_leaves_allocated_before_effective_date(self):
    method test_pro_rated_allocation_via_scheduler (line 415) | def test_pro_rated_allocation_via_scheduler(self):
    method test_get_earned_leave_details_for_dashboard (line 438) | def test_get_earned_leave_details_for_dashboard(self):
    method test_allocate_leaves_manually (line 485) | def test_allocate_leaves_manually(self):
    method test_quarterly_earned_leaves_allocated_on_last_day_in_the_middle_of_leave_period (line 524) | def test_quarterly_earned_leaves_allocated_on_last_day_in_the_middle_o...
    method test_quarterly_earned_leaves_allocated_on_last_day_at_the_start_of_the_leave_period (line 547) | def test_quarterly_earned_leaves_allocated_on_last_day_at_the_start_of...
    method test_quartertly_earned_leaves_allocated_on_first_day_at_the_start_of_leave_period (line 568) | def test_quartertly_earned_leaves_allocated_on_first_day_at_the_start_...
    method test_quarterly_earned_leaves_allocated_by_the_scheduler (line 589) | def test_quarterly_earned_leaves_allocated_by_the_scheduler(self):
    method test_quarterly_leaves_allocated_pro_rated (line 626) | def test_quarterly_leaves_allocated_pro_rated(self):
    method test_half_yearly_earned_leaves_allocated_on_last_day_at_the_start_of_leave_period (line 664) | def test_half_yearly_earned_leaves_allocated_on_last_day_at_the_start_...
    method test_half_yearly_earned_leaves_allocated_on_last_day_in_the_middle_of_leave_period (line 685) | def test_half_yearly_earned_leaves_allocated_on_last_day_in_the_middle...
    method test_half_yearly_earned_leaves_allocated_on_first_day_at_the_start_of_leave_period (line 706) | def test_half_yearly_earned_leaves_allocated_on_first_day_at_the_start...
    method test_half_yearly_earned_leaves_allocated_by_the_scheduler (line 727) | def test_half_yearly_earned_leaves_allocated_by_the_scheduler(self):
    method test_half_yearly_leaves_allocated_pro_rated (line 759) | def test_half_yearly_leaves_allocated_pro_rated(self):
    method test_yearly_leaves_allocated_on_last_day_at_the_start_of_the_period (line 795) | def test_yearly_leaves_allocated_on_last_day_at_the_start_of_the_perio...
    method test_yearly_leaves_allocated_on_last_day_in_the_middle_of_the_period (line 816) | def test_yearly_leaves_allocated_on_last_day_in_the_middle_of_the_peri...
    method test_yearly_leaves_allocated_on_first_day_at_the_start_of_the_period (line 837) | def test_yearly_leaves_allocated_on_first_day_at_the_start_of_the_peri...
    method test_yearly_leaves_allocated_by_scheduler (line 858) | def test_yearly_leaves_allocated_by_scheduler(self):
    method test_yearly_leaves_allocated_pro_rated (line 889) | def test_yearly_leaves_allocated_pro_rated(self):
    method test_error_logging_failed_allocations (line 925) | def test_error_logging_failed_allocations(self):
    method test_send_email_for_failed_allocations (line 949) | def test_send_email_for_failed_allocations(self):
    method test_retry_failed_allocations (line 975) | def test_retry_failed_allocations(self):
    method test_permission_check_for_retrying_failed_allocation (line 1016) | def test_permission_check_for_retrying_failed_allocation(self):
    method test_allocating_earned_leave_when_schedule_doesnt_exist (line 1047) | def test_allocating_earned_leave_when_schedule_doesnt_exist(self):
  function create_earned_leave_type (line 1107) | def create_earned_leave_type(
  function create_leave_period (line 1126) | def create_leave_period(name, start_date=None, end_date=None):
  function make_policy_assignment (line 1142) | def make_policy_assignment(
  function get_allocated_leaves (line 1178) | def get_allocated_leaves(assignment):
  function allocate_earned_leaves_for_months (line 1186) | def allocate_earned_leaves_for_months(months):

FILE: hrms/hr/doctype/leave_allocation/test_leave_allocation.py
  class TestLeaveAllocation (line 15) | class TestLeaveAllocation(HRMSTestSuite):
    method setUp (line 16) | def setUp(self):
    method test_overlapping_allocation (line 20) | def test_overlapping_allocation(self):
    method test_invalid_period (line 48) | def test_invalid_period(self):
    method test_validation_for_over_allocation (line 65) | def test_validation_for_over_allocation(self):
    method test_validation_for_over_allocation_post_submission (line 103) | def test_validation_for_over_allocation_post_submission(self):
    method test_validation_for_over_allocation_based_on_leave_setup (line 121) | def test_validation_for_over_allocation_based_on_leave_setup(self):
    method test_validation_for_over_allocation_based_on_leave_setup_post_submission (line 158) | def test_validation_for_over_allocation_based_on_leave_setup_post_subm...
    method test_validate_back_dated_allocation_update (line 202) | def test_validate_back_dated_allocation_update(self):
    method test_carry_forward_calculation (line 230) | def test_carry_forward_calculation(self):
    method test_precision (line 279) | def test_precision(self):
    method test_carry_forward_leaves_expiry (line 310) | def test_carry_forward_leaves_expiry(self):
    method test_carry_forward_leaves_expiry_after_partially_used_leaves (line 354) | def test_carry_forward_leaves_expiry_after_partially_used_leaves(self):
    method test_carry_forward_leaves_expiry_after_completely_used_leaves (line 405) | def test_carry_forward_leaves_expiry_after_completely_used_leaves(self):
    method test_creation_of_leave_ledger_entry_on_submit (line 455) | def test_creation_of_leave_ledger_entry_on_submit(self):
    method test_leave_addition_after_submit (line 474) | def test_leave_addition_after_submit(self):
    method test_leave_addition_after_submit_with_carry_forward (line 497) | def test_leave_addition_after_submit_with_carry_forward(self):
    method test_leave_subtraction_after_submit (line 526) | def test_leave_subtraction_after_submit(self):
    method test_leave_subtraction_after_submit_with_carry_forward (line 549) | def test_leave_subtraction_after_submit_with_carry_forward(self):
    method test_validation_against_leave_application_after_submit (line 576) | def test_validation_against_leave_application_after_submit(self):
  function create_leave_allocation (line 612) | def create_leave_allocation(**args):

FILE: hrms/hr/doctype/leave_application/leave_application.js
  method set_employee (line 118) | async set_employee(frm) {
  method half_day_date (line 170) | half_day_date(frm) {

FILE: hrms/hr/doctype/leave_application/leave_application.py
  class LeaveDayBlockedError (line 42) | class LeaveDayBlockedError(frappe.ValidationError):
  class OverlapError (line 46) | class OverlapError(frappe.ValidationError):
  class AttendanceAlreadyMarkedError (line 50) | class AttendanceAlreadyMarkedError(frappe.ValidationError):
  class NotAnOptionalHoliday (line 54) | class NotAnOptionalHoliday(frappe.ValidationError):
  class InsufficientLeaveBalanceError (line 58) | class InsufficientLeaveBalanceError(frappe.ValidationError):
  class LeaveAcrossAllocationsError (line 62) | class LeaveAcrossAllocationsError(frappe.ValidationError):
  class LeaveApplication (line 69) | class LeaveApplication(Document, PWANotificationsMixin):
    method get_feed (line 102) | def get_feed(self):
    method after_insert (line 105) | def after_insert(self):
    method validate (line 108) | def validate(self):
    method on_update (line 124) | def on_update(self):
    method on_submit (line 134) | def on_submit(self):
    method before_cancel (line 162) | def before_cancel(self):
    method on_discard (line 165) | def on_discard(self):
    method on_cancel (line 168) | def on_cancel(self):
    method after_delete (line 177) | def after_delete(self):
    method publish_update (line 180) | def publish_update(self):
    method validate_applicable_after (line 185) | def validate_applicable_after(self):
    method validate_dates (line 206) | def validate_dates(self):
    method validate_dates_across_allocation (line 246) | def validate_dates_across_allocation(self):
    method get_allocation_based_on_application_dates (line 260) | def get_allocation_based_on_application_dates(self) -> tuple[dict, dict]:
    method validate_back_dated_application (line 283) | def validate_back_dated_application(self):
    method get_leave_allocation (line 299) | def get_leave_allocation(self):
    method update_attendance (line 315) | def update_attendance(self):
    method create_or_update_attendance (line 348) | def create_or_update_attendance(self, attendance_name, date):
    method cancel_attendance (line 383) | def cancel_attendance(self):
    method validate_salary_processed_days (line 394) | def validate_salary_processed_days(self):
    method show_block_day_warning (line 415) | def show_block_day_warning(self):
    method validate_block_days (line 430) | def validate_block_days(self):
    method validate_balance_leaves (line 438) | def validate_balance_leaves(self):
    method show_insufficient_balance_message (line 475) | def show_insufficient_balance_message(self, leave_balance_for_consumpt...
    method validate_leave_overlap (line 500) | def validate_leave_overlap(self):
    method throw_overlap_error (line 536) | def throw_overlap_error(self, d):
    method get_total_leaves_on_half_day (line 543) | def get_total_leaves_on_half_day(self):
    method validate_max_days (line 557) | def validate_max_days(self):
    method get_consecutive_leave_details (line 577) | def get_consecutive_leave_details(self) -> dict:
    method validate_attendance (line 634) | def validate_attendance(self):
    method validate_optional_leave (line 663) | def validate_optional_leave(self):
    method set_half_day_date (line 684) | def set_half_day_date(self):
    method notify_employee (line 691) | def notify_employee(self):
    method notify_leave_approver (line 719) | def notify_leave_approver(self):
    method notify (line 744) | def notify(self, args):
    method create_leave_ledger_entry (line 768) | def create_leave_ledger_entry(self, submit=True):
    method is_separate_ledger_entry_required (line 797) | def is_separate_ledger_entry_required(
    method create_separate_ledger_entries (line 809) | def create_separate_ledger_entries(self, alloc_on_from_date, alloc_on_...
    method create_ledger_entry_for_intermediate_allocation_expiry (line 870) | def create_ledger_entry_for_intermediate_allocation_expiry(self, expir...
    method validate_for_self_approval (line 899) | def validate_for_self_approval(self):
    method onload (line 911) | def onload(self):
  function get_allocation_expiry_for_cf_leaves (line 918) | def get_allocation_expiry_for_cf_leaves(
  function get_number_of_leave_days (line 941) | def get_number_of_leave_days(
  function get_leave_details (line 969) | def get_leave_details(employee: str, date: str | datetime.date, for_sala...
  function get_leave_balance_on (line 1008) | def get_leave_balance_on(
  function get_leave_allocation_records (line 1055) | def get_leave_allocation_records(employee, date, leave_type=None):
  function get_leaves_pending_approval_for_period (line 1134) | def get_leaves_pending_approval_for_period(
  function get_remaining_leaves (line 1150) | def get_remaining_leaves(
  function get_manually_expired_leaves (line 1199) | def get_manually_expired_leaves(
  function get_new_and_cf_leaves_taken (line 1220) | def get_new_and_cf_leaves_taken(allocation: dict, cf_expiry: str) -> tup...
  function get_leaves_for_period (line 1238) | def get_leaves_for_period(
  function get_leave_entries (line 1295) | def get_leave_entries(employee, leave_type, from_date, to_date):
  function get_holidays (line 1317) | def get_holidays(employee: str, from_date: str | datetime.date, to_date:...
  function is_lwp (line 1323) | def is_lwp(leave_type):
  function get_events (line 1329) | def get_events(start: str, end: str, filters: str | None = None) -> list...
  function add_department_leaves (line 1360) | def add_department_leaves(events, start, end, employee, company):
  function add_leaves (line 1369) | def add_leaves(events, start, end, filters=None):
  function add_block_dates (line 1408) | def add_block_dates(events, start, end, employee, company):
  function add_holidays (line 1426) | def add_holidays(events, start, end, employee, company):
  function get_mandatory_approval (line 1450) | def get_mandatory_approval(doctype: str) -> str | int | bool:
  function get_approved_leaves_for_period (line 1460) | def get_approved_leaves_for_period(employee, leave_type, from_date, to_d...
  function get_leave_approver (line 1506) | def get_leave_approver(employee: str) -> str:
  function on_doctype_update (line 1519) | def on_doctype_update():

FILE: hrms/hr/doctype/leave_application/leave_application_dashboard.py
  function get_data (line 4) | def get_data():

FILE: hrms/hr/doctype/leave_application/test_leave_application.py
  class TestLeaveApplication (line 49) | class TestLeaveApplication(HRMSTestSuite):
    method setUp (line 50) | def setUp(self):
    method _clear_roles (line 67) | def _clear_roles(self):
    method get_application (line 73) | def get_application(self, doc):
    method test_validate_application_across_allocations (line 80) | def test_validate_application_across_allocations(self):
    method test_insufficient_leave_balance_validation (line 126) | def test_insufficient_leave_balance_validation(self):
    method test_separate_leave_ledger_entry_for_boundary_applications (line 165) | def test_separate_leave_ledger_entry_for_boundary_applications(self):
    method test_overwrite_attendance (line 232) | def test_overwrite_attendance(self):
    method test_overwrite_half_day_attendance (line 259) | def test_overwrite_half_day_attendance(self):
    method test_attendance_for_include_holidays (line 283) | def test_attendance_for_include_holidays(self):
    method test_attendance_update_for_exclude_holidays (line 308) | def test_attendance_update_for_exclude_holidays(self):
    method test_block_list (line 355) | def test_block_list(self):
    method test_overlap (line 379) | def test_overlap(self):
    method test_overlap_with_half_day_1 (line 396) | def test_overlap_with_half_day_1(self):
    method test_overlap_with_half_day_2 (line 430) | def test_overlap_with_half_day_2(self):
    method test_overlap_with_half_day_3 (line 453) | def test_overlap_with_half_day_3(self):
    method test_optional_leave (line 489) | def test_optional_leave(self):
    method test_leaves_allowed (line 543) | def test_leaves_allowed(self):
    method test_applicable_after (line 585) | def test_applicable_after(self):
    method test_max_continuous_leaves (line 636) | def test_max_continuous_leaves(self):
    method test_max_consecutive_leaves_across_leave_applications (line 670) | def test_max_consecutive_leaves_across_leave_applications(self):
    method test_leave_balance_near_allocaton_expiry (line 727) | def test_leave_balance_near_allocaton_expiry(self):
    method test_current_leave_on_submit (line 744) | def test_current_leave_on_submit(self):
    method test_creation_of_leave_ledger_entry_on_submit (line 780) | def test_creation_of_leave_ledger_entry_on_submit(self):
    method test_ledger_entry_creation_on_intermediate_allocation_expiry (line 816) | def test_ledger_entry_creation_on_intermediate_allocation_expiry(self):
    method test_leave_application_creation_after_expiry (line 854) | def test_leave_application_creation_after_expiry(self):
    method test_leave_approver_perms (line 872) | def test_leave_approver_perms(self):
    method test_self_leave_approval_allowed (line 911) | def test_self_leave_approval_allowed(self):
    method test_self_leave_approval_not_allowed (line 945) | def test_self_leave_approval_not_allowed(self):
    method test_get_leave_details_for_dashboard (line 987) | def test_get_leave_details_for_dashboard(self):
    method test_leave_details_with_expired_cf_leaves (line 1023) | def test_leave_details_with_expired_cf_leaves(self):
    method test_leave_details_with_application_across_cf_expiry (line 1057) | def test_leave_details_with_application_across_cf_expiry(self):
    method test_leave_details_with_application_across_cf_expiry_2 (line 1093) | def test_leave_details_with_application_across_cf_expiry_2(self):
    method test_leave_details_with_application_after_cf_expiry (line 1133) | def test_leave_details_with_application_after_cf_expiry(self):
    method test_get_leave_allocation_records (line 1169) | def test_get_leave_allocation_records(self):
    method test_filtered_old_cf_entries_in_get_leave_allocation_records (line 1202) | def test_filtered_old_cf_entries_in_get_leave_allocation_records(self):
    method test_modifying_attendance_when_half_day_exists_from_checkins (line 1224) | def test_modifying_attendance_when_half_day_exists_from_checkins(self):
    method test_modifying_attendance_from_absent_to_half_day (line 1257) | def test_modifying_attendance_from_absent_to_half_day(self):
    method test_half_day_status_for_two_half_leaves (line 1290) | def test_half_day_status_for_two_half_leaves(self):
    method test_leave_balance_when_allocation_is_expired_manually (line 1332) | def test_leave_balance_when_allocation_is_expired_manually(self):
    method test_backdated_application_after_expiry (line 1352) | def test_backdated_application_after_expiry(self):
    method test_leave_days_across_two_holiday_lists (line 1383) | def test_leave_days_across_two_holiday_lists(self):
    method test_status_on_discard (line 1417) | def test_status_on_discard(self):
  function create_carry_forwarded_allocation (line 1426) | def create_carry_forwarded_allocation(employee, leave_type, date=None):
  function make_allocation_record (line 1454) | def make_allocation_record(
  function get_employee (line 1475) | def get_employee():
  function get_leave_period (line 1479) | def get_leave_period(current=False):
  function allocate_leaves (line 1496) | def allocate_leaves(employee, leave_period, leave_type, new_leaves_alloc...

FILE: hrms/hr/doctype/leave_block_list/leave_block_list.js
  method primary_action (line 85) | primary_action(values) {

FILE: hrms/hr/doctype/leave_block_list/leave_block_list.py
  class LeaveBlockList (line 13) | class LeaveBlockList(Document):
    method validate (line 33) | def validate(self):
    method set_weekly_off_dates (line 42) | def set_weekly_off_dates(self, start_date: str, end_date: str, days: l...
    method get_block_dates_from_date (line 47) | def get_block_dates_from_date(self, start_date, end_date, days):
  function get_applicable_block_dates (line 64) | def get_applicable_block_dates(
  function get_applicable_block_lists (line 77) | def get_applicable_block_lists(employee=None, company=None, all_lists=Fa...
  function is_user_in_allow_list (line 111) | def is_user_in_allow_list(block_list):

FILE: hrms/hr/doctype/leave_block_list/leave_block_list_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/leave_block_list/test_leave_block_list.py
  class TestLeaveBlockList (line 11) | class TestLeaveBlockList(HRMSTestSuite):
    method test_get_applicable_block_dates (line 12) | def test_get_applicable_block_dates(self):
    method test_get_applicable_block_dates_for_allowed_user (line 22) | def test_get_applicable_block_dates_for_allowed_user(self):
    method test_get_applicable_block_dates_all_lists (line 29) | def test_get_applicable_block_dates_all_lists(self):
    method test_get_applicable_block_dates_all_lists_for_leave_type (line 39) | def test_get_applicable_block_dates_all_lists_for_leave_type(self):
    method test_get_applicable_block_dates_for_allowed_user_for_leave_type (line 54) | def test_get_applicable_block_dates_for_allowed_user_for_leave_type(se...

FILE: hrms/hr/doctype/leave_block_list_allow/leave_block_list_allow.py
  class LeaveBlockListAllow (line 10) | class LeaveBlockListAllow(Document):

FILE: hrms/hr/doctype/leave_block_list_date/leave_block_list_date.py
  class LeaveBlockListDate (line 10) | class LeaveBlockListDate(Document):

FILE: hrms/hr/doctype/leave_control_panel/leave_control_panel.js
  method employment_type (line 41) | employment_type(frm) {
  method branch (line 45) | branch(frm) {
  method department (line 49) | department(frm) {
  method designation (line 53) | designation(frm) {
  method employee_grade (line 57) | employee_grade(frm) {
  method dates_based_on (line 61) | dates_based_on(frm) {
  method from_date (line 66) | from_date(frm) {
  method to_date (line 70) | to_date(frm) {
  method leave_period (line 74) | leave_period(frm) {
  method allocate_based_on_leave_policy (line 78) | allocate_based_on_leave_policy(frm) {
  method leave_type (line 82) | leave_type(frm) {
  method leave_policy (line 86) | leave_policy(frm) {
  method reset_leave_details (line 90) | reset_leave_details(frm) {
  method set_leave_details (line 97) | set_leave_details(frm) {
  method get_employees (line 114) | get_employees(frm) {
  method get_employees_datatable_columns (line 127) | get_employees_datatable_columns() {
  method set_query (line 158) | set_query(frm) {
  method set_primary_action (line 175) | set_primary_action(frm) {
  method allocate_leave (line 181) | allocate_leave(frm) {
  method bulk_allocate_leave (line 197) | bulk_allocate_leave(frm, employees) {

FILE: hrms/hr/doctype/leave_control_panel/leave_control_panel.py
  class LeaveControlPanel (line 14) | class LeaveControlPanel(Document):
    method validate_fields (line 40) | def validate_fields(self, employees: list):
    method allocate_leave (line 56) | def allocate_leave(self, employees: list):
    method create_leave_allocations (line 62) | def create_leave_allocations(self, employees: list) -> dict:
    method create_leave_policy_assignments (line 98) | def create_leave_policy_assignments(self, employees: list) -> dict:
    method get_from_to_date (line 139) | def get_from_to_date(self):
    method get_employees (line 148) | def get_employees(self, advanced_filters: list) -> list:
    method get_employees_without_allocations (line 161) | def get_employees_without_allocations(self, all_employees: list, from_...
    method get_latest_leave_period (line 200) | def get_latest_leave_period(self):
    method get_filters (line 211) | def get_filters(self):

FILE: hrms/hr/doctype/leave_control_panel/test_leave_control_panel.py
  class TestLeaveControlPanel (line 18) | class TestLeaveControlPanel(HRMSTestSuite):
    method setUp (line 19) | def setUp(self):
    method create_records (line 22) | def create_records(self):
    method test_allocation_based_on_leave_type (line 46) | def test_allocation_based_on_leave_type(self):
    method test_allocation_based_on_leave_policy_assignment (line 70) | def test_allocation_based_on_leave_policy_assignment(self):
    method test_allocation_based_on_joining_date (line 92) | def test_allocation_based_on_joining_date(self):
    method test_get_employees (line 117) | def test_get_employees(self):

FILE: hrms/hr/doctype/leave_encashment/leave_encashment.py
  class LeaveEncashment (line 21) | class LeaveEncashment(AccountsController):
    method validate (line 54) | def validate(self):
    method set_salary_structure (line 64) | def set_salary_structure(self):
    method before_submit (line 73) | def before_submit(self):
    method on_submit (line 77) | def on_submit(self):
    method on_cancel (line 89) | def on_cancel(self):
    method get_leave_details_for_encashment (line 111) | def get_leave_details_for_encashment(self):
    method get_encashment_settings (line 117) | def get_encashment_settings(self):
    method set_actual_encashable_days (line 125) | def set_actual_encashable_days(self):
    method set_encashment_days (line 155) | def set_encashment_days(self):
    method set_leave_balance (line 168) | def set_leave_balance(self):
    method create_additional_salary (line 187) | def create_additional_salary(self):
    method set_encashed_leaves_in_allocation (line 203) | def set_encashed_leaves_in_allocation(self):
    method set_encashment_amount (line 212) | def set_encashment_amount(self):
    method set_status (line 239) | def set_status(self, update=False):
    method get_leave_allocation (line 259) | def get_leave_allocation(self):
    method create_leave_ledger_entry (line 282) | def create_leave_ledger_entry(self, submit=True):
    method set_total_advance_paid (line 305) | def set_total_advance_paid(self):
    method create_gl_entries (line 325) | def create_gl_entries(self, cancel=False):
    method get_gl_entries (line 329) | def get_gl_entries(self):
    method on_discard (line 366) | def on_discard(self):
  function create_leave_encashment (line 370) | def create_leave_encashment(leave_allocation):

FILE: hrms/hr/doctype/leave_encashment/test_leave_encashment.py
  class TestLeaveEncashment (line 27) | class TestLeaveEncashment(HRMSTestSuite):
    method setUp (line 28) | def setUp(self):
    method test_leave_balance_value_and_amount (line 63) | def test_leave_balance_value_and_amount(self):
    method test_non_encashable_leaves_setting (line 80) | def test_non_encashable_leaves_setting(self):
    method test_max_encashable_leaves_setting (line 116) | def test_max_encashable_leaves_setting(self):
    method test_max_encashable_leaves_and_non_encashable_leaves_setting (line 151) | def test_max_encashable_leaves_and_non_encashable_leaves_setting(self):
    method test_creation_of_leave_ledger_entry_on_submit (line 187) | def test_creation_of_leave_ledger_entry_on_submit(self):
    method test_unused_leaves_after_leave_encashment_for_carry_forwarding_leave_type (line 206) | def test_unused_leaves_after_leave_encashment_for_carry_forwarding_lea...
    method test_leave_expiry_after_leave_encashment_for_non_carry_forwarding_leave_type (line 234) | def test_leave_expiry_after_leave_encashment_for_non_carry_forwarding_...
    method get_encashment_created_after_leave_period (line 266) | def get_encashment_created_after_leave_period(self, employee, is_carry...
    method test_status_of_leave_encashment_after_payment_via_salary_slip (line 305) | def test_status_of_leave_encashment_after_payment_via_salary_slip(self):
    method test_status_of_leave_encashment_after_payment_via_payment_entry_and_fnf (line 339) | def test_status_of_leave_encashment_after_payment_via_payment_entry_an...
    method create_test_leave_encashment (line 398) | def create_test_leave_encashment(self, **kwargs):
    method test_status_on_discard (line 410) | def test_status_on_discard(self):
    method test_leave_encashment_based_on_salary_structure_assignment (line 417) | def test_leave_encashment_based_on_salary_structure_assignment(self):
  function create_leave_encashment (line 446) | def create_leave_encashment(**args):

FILE: hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
  class InvalidLeaveLedgerEntry (line 11) | class InvalidLeaveLedgerEntry(frappe.ValidationError):
  class LeaveLedgerEntry (line 15) | class LeaveLedgerEntry(Document):
    method validate (line 40) | def validate(self):
    method on_cancel (line 53) | def on_cancel(self):
  function validate_leave_allocation_against_leave_application (line 61) | def validate_leave_allocation_against_leave_application(ledger):
  function create_leave_ledger_entry (line 89) | def create_leave_ledger_entry(ref_doc, args, submit=True):
  function delete_ledger_entry (line 111) | def delete_ledger_entry(ledger):
  function get_previous_expiry_ledger_entry (line 127) | def get_previous_expiry_ledger_entry(ledger):
  function process_expired_allocation (line 155) | def process_expired_allocation():
  function create_expiry_ledger_entry (line 199) | def create_expiry_ledger_entry(allocations):
  function get_remaining_leaves (line 208) | def get_remaining_leaves(allocation):
  function expire_allocation (line 223) | def expire_allocation(allocation: str | Document | frappe._dict, expiry_...
  function expire_carried_forward_allocation (line 250) | def expire_carried_forward_allocation(allocation):
  function on_doctype_update (line 277) | def on_doctype_update():

FILE: hrms/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py
  class TestLeaveLedgerEntry (line 13) | class TestLeaveLedgerEntry(HRMSTestSuite):
    method setUp (line 14) | def setUp(self):
    method test_expire_allocation (line 18) | def test_expire_allocation(self):

FILE: hrms/hr/doctype/leave_period/leave_period.py
  class LeavePeriod (line 13) | class LeavePeriod(Document):
    method validate (line 29) | def validate(self):
    method validate_dates (line 33) | def validate_dates(self):

FILE: hrms/hr/doctype/leave_period/leave_period_dashboard.py
  function get_data (line 4) | def get_data():

FILE: hrms/hr/doctype/leave_period/test_leave_period.py
  function create_leave_period (line 11) | def create_leave_period(from_date, to_date, company=None):

FILE: hrms/hr/doctype/leave_policy/leave_policy.py
  class LeavePolicy (line 10) | class LeavePolicy(Document):
    method validate (line 26) | def validate(self):

FILE: hrms/hr/doctype/leave_policy/leave_policy_dashboard.py
  function get_data (line 4) | def get_data():

FILE: hrms/hr/doctype/leave_policy/test_leave_policy.py
  class TestLeavePolicy (line 9) | class TestLeavePolicy(HRMSTestSuite):
    method test_max_leave_allowed (line 10) | def test_max_leave_allowed(self):
  function create_leave_policy (line 25) | def create_leave_policy(**args):

FILE: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
  class LeavePolicyAssignment (line 30) | class LeavePolicyAssignment(Document):
    method validate (line 52) | def validate(self):
    method on_submit (line 57) | def on_submit(self):
    method set_dates (line 60) | def set_dates(self):
    method validate_policy_assignment_overlap (line 70) | def validate_policy_assignment_overlap(self):
    method warn_about_carry_forwarding (line 94) | def warn_about_carry_forwarding(self):
    method grant_leave_alloc_for_employee (line 109) | def grant_leave_alloc_for_employee(self):
    method create_leave_allocation (line 135) | def create_leave_allocation(self, annual_allocation, leave_details, da...
    method get_new_leaves (line 183) | def get_new_leaves(self, annual_allocation, leave_details, date_of_joi...
    method get_leaves_for_passed_period (line 213) | def get_leaves_for_passed_period(self, annual_allocation, leave_detail...
    method get_current_and_from_date (line 230) | def get_current_and_from_date(self, date_of_joining):
    method get_periods_passed (line 241) | def get_periods_passed(self, earned_leave_frequency, current_date, fro...
    method calculate_leaves_for_passed_period (line 255) | def calculate_leaves_for_passed_period(
    method get_earned_leave_schedule (line 291) | def get_earned_leave_schedule(
  function get_pro_rata_period_end_date (line 365) | def get_pro_rata_period_end_date(consider_current_month):
  function calculate_periods_passed (line 378) | def calculate_periods_passed(
  function is_earned_leave_applicable_for_current_period (line 393) | def is_earned_leave_applicable_for_current_period(date_of_joining, alloc...
  function calculate_pro_rated_leaves (line 420) | def calculate_pro_rated_leaves(
  function create_assignment_for_multiple_employees (line 438) | def create_assignment_for_multiple_employees(employees: str | list[str],...
  function create_assignment (line 468) | def create_assignment(employee: str, data: frappe._dict) -> Document:
  function show_assignment_submission_status (line 481) | def show_assignment_submission_status(failed):
  function get_leave_type_details (line 501) | def get_leave_type_details():

FILE: hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
  function get_data (line 4) | def get_data():

FILE: hrms/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
  class TestLeavePolicyAssignment (line 18) | class TestLeavePolicyAssignment(HRMSTestSuite):
    method setUp (line 19) | def setUp(self):
    method test_grant_leaves (line 24) | def test_grant_leaves(self):
    method test_allow_to_grant_all_leave_after_cancellation_of_every_leave_allocation (line 58) | def test_allow_to_grant_all_leave_after_cancellation_of_every_leave_al...
    method test_pro_rated_leave_allocation (line 89) | def test_pro_rated_leave_allocation(self):
    method test_get_leaves_for_passed_months (line 112) | def test_get_leaves_for_passed_months(self):
    method test_pro_rated_leave_allocation_for_custom_date_range (line 154) | def test_pro_rated_leave_allocation_for_custom_date_range(self):
    method test_earned_leave_allocation_if_leave_policy_assignment_submitted_after_period (line 198) | def test_earned_leave_allocation_if_leave_policy_assignment_submitted_...
    method test_earned_leave_allocation_for_leave_period_spanning_two_years (line 225) | def test_earned_leave_allocation_for_leave_period_spanning_two_years(s...
    method test_skip_zero_allocation_leaves (line 254) | def test_skip_zero_allocation_leaves(self):

FILE: hrms/hr/doctype/leave_policy_detail/leave_policy_detail.py
  class LeavePolicyDetail (line 8) | class LeavePolicyDetail(Document):

FILE: hrms/hr/doctype/leave_policy_detail/test_leave_policy_detail.py
  class TestLeavePolicyDetail (line 7) | class TestLeavePolicyDetail(HRMSTestSuite):

FILE: hrms/hr/doctype/leave_type/leave_type.py
  class LeaveType (line 11) | class LeaveType(Document):
    method validate (line 45) | def validate(self):
    method validate_lwp (line 50) | def validate_lwp(self):
    method validate_leave_types (line 65) | def validate_leave_types(self):
    method validate_allocated_earned_leave (line 86) | def validate_allocated_earned_leave(self):
    method clear_cache (line 107) | def clear_cache(self):

FILE: hrms/hr/doctype/leave_type/leave_type_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/leave_type/test_leave_type.py
  function create_leave_type (line 9) | def create_leave_type(**args):

FILE: hrms/hr/doctype/offer_term/offer_term.py
  class OfferTerm (line 8) | class OfferTerm(Document):

FILE: hrms/hr/doctype/offer_term/test_offer_term.py
  class TestOfferTerm (line 9) | class TestOfferTerm(HRMSTestSuite):

FILE: hrms/hr/doctype/overtime_details/overtime_details.py
  class OvertimeDetails (line 8) | class OvertimeDetails(Document):

FILE: hrms/hr/doctype/overtime_salary_component/overtime_salary_component.py
  class OvertimeSalaryComponent (line 8) | class OvertimeSalaryComponent(Document):

FILE: hrms/hr/doctype/overtime_slip/overtime_slip.js
  method employee (line 23) | employee(frm) {
  method posting_date (line 26) | posting_date(frm) {

FILE: hrms/hr/doctype/overtime_slip/overtime_slip.py
  class OvertimeSlip (line 21) | class OvertimeSlip(Document):
    method validate (line 47) | def validate(self):
    method on_submit (line 57) | def on_submit(self):
    method validate_overlap (line 60) | def validate_overlap(self):
    method validate_overtime_date_and_duration (line 78) | def validate_overtime_date_and_duration(self):
    method get_frequency_and_dates (line 104) | def get_frequency_and_dates(self):
    method get_emp_and_overtime_details (line 123) | def get_emp_and_overtime_details(self):
    method create_overtime_details_row_for_attendance (line 135) | def create_overtime_details_row_for_attendance(self, records):
    method get_attendance_records (line 167) | def get_attendance_records(self):
    method process_overtime_slip (line 195) | def process_overtime_slip(self):
    method create_additional_salary (line 202) | def create_additional_salary(self, salary_component, total_amount, pre...
    method get_overtime_component_amounts (line 219) | def get_overtime_component_amounts(self):
    method _bulk_load_overtime_types (line 254) | def _bulk_load_overtime_types(self, overtime_type_names):
    method _get_applicable_hourly_rate (line 306) | def _get_applicable_hourly_rate(self, overtime_type, standard_working_...
    method _calculate_component_based_hourly_rate (line 319) | def _calculate_component_based_hourly_rate(self, overtime_type, standa...
    method _make_salary_slip (line 339) | def _make_salary_slip(self, salary_structure):
    method calculate_overtime_amount (line 349) | def calculate_overtime_amount(
    method get_holiday_map (line 375) | def get_holiday_map(self):
    method get_overtime_type_details (line 391) | def get_overtime_type_details(self, name):
  function filter_employees_for_overtime_slip_creation (line 420) | def filter_employees_for_overtime_slip_creation(start_date, end_date, em...
  function create_overtime_slips_for_employees (line 470) | def create_overtime_slips_for_employees(employees, args):
  function submit_overtime_slips_for_employees (line 502) | def submit_overtime_slips_for_employees(overtime_slips, payroll_entry):

FILE: hrms/hr/doctype/overtime_slip/test_overtime_slip.py
  class TestOvertimeSlip (line 17) | class TestOvertimeSlip(HRMSTestSuite):
    method test_overtime_calculation_and_additional_salary_creation (line 18) | def test_overtime_calculation_and_additional_salary_creation(self):
    method test_overtime_calculation_for_fixed_hourly_rate (line 65) | def test_overtime_calculation_for_fixed_hourly_rate(self):
    method test_overtime_slip_creation_via_payroll_entry (line 82) | def test_overtime_slip_creation_via_payroll_entry(self):
  function create_overtime_slip (line 139) | def create_overtime_slip(employee):
  function create_checkin_records_for_overtime (line 151) | def create_checkin_records_for_overtime(employee):
  function setup_overtime (line 164) | def setup_overtime(employee, overtime_calculation_method="Salary Compone...

FILE: hrms/hr/doctype/overtime_type/overtime_type.py
  class OvertimeType (line 9) | class OvertimeType(Document):
    method validate (line 34) | def validate(self):
    method validate_applicable_components (line 38) | def validate_applicable_components(self):

FILE: hrms/hr/doctype/overtime_type/test_overtime_type.py
  class TestOvertimeType (line 11) | class TestOvertimeType(UnitTestCase):
  function create_overtime_type (line 20) | def create_overtime_type(**args):

FILE: hrms/hr/doctype/purpose_of_travel/purpose_of_travel.py
  class PurposeofTravel (line 8) | class PurposeofTravel(Document):

FILE: hrms/hr/doctype/purpose_of_travel/test_purpose_of_travel.py
  class TestPurposeofTravel (line 7) | class TestPurposeofTravel(HRMSTestSuite):

FILE: hrms/hr/doctype/pwa_notification/pwa_notification.py
  class PWANotification (line 9) | class PWANotification(Document):
    method on_update (line 27) | def on_update(self):
    method after_insert (line 30) | def after_insert(self):
    method send_push_notification (line 33) | def send_push_notification(self):
    method get_notification_link (line 52) | def get_notification_link(self):

FILE: hrms/hr/doctype/pwa_notification/test_pwa_notification.py
  class TestPWANotification (line 8) | class TestPWANotification(HRMSTestSuite):

FILE: hrms/hr/doctype/shift_assignment/shift_assignment.py
  class OverlappingShiftError (line 17) | class OverlappingShiftError(frappe.ValidationError):
  class MultipleShiftError (line 21) | class MultipleShiftError(frappe.ValidationError):
  class ShiftAssignment (line 25) | class ShiftAssignment(Document):
    method validate (line 49) | def validate(self):
    method on_update_after_submit (line 55) | def on_update_after_submit(self):
    method on_cancel (line 60) | def on_cancel(self):
    method validate_employee_checkin (line 64) | def validate_employee_checkin(self):
    method validate_attendance (line 81) | def validate_attendance(self):
    method validate_overlapping_shifts (line 98) | def validate_overlapping_shifts(self):
    method validate_same_date_multiple_shifts (line 110) | def validate_same_date_multiple_shifts(self, overlapping_dates):
    method get_overlapping_dates (line 138) | def get_overlapping_dates(self):
    method throw_overlap_error (line 160) | def throw_overlap_error(self, shift_details):
  function has_overlapping_timings (line 173) | def has_overlapping_timings(shift_1: str, shift_2: str) -> bool:
  function get_events (line 189) | def get_events(start: str | date, end: str | date, filters: list | None ...
  function get_shift_assignments (line 202) | def get_shift_assignments(start: str, end: str, filters: str | list | No...
  function get_shift_events (line 230) | def get_shift_events(assignments: list[dict]) -> list[dict]:
  function get_shift_type_timing (line 268) | def get_shift_type_timing(shift_types):
  function get_shift_for_time (line 282) | def get_shift_for_time(shifts: list[dict], for_timestamp: datetime) -> d...
  function _is_shift_outside_assignment_period (line 302) | def _is_shift_outside_assignment_period(shift_details: dict, assignment:...
  function _is_shift_start_before_assignment (line 319) | def _is_shift_start_before_assignment(shift_details: dict, assignment: d...
  function _is_shift_end_after_assignment (line 339) | def _is_shift_end_after_assignment(shift_details: dict, assignment: dict...
  function _is_timestamp_within_shift (line 365) | def _is_timestamp_within_shift(shift_details: dict, for_timestamp: datet...
  function _adjust_overlapping_shifts (line 370) | def _adjust_overlapping_shifts(shifts: dict):
  function get_shifts_for_date (line 387) | def get_shifts_for_date(employee: str, for_timestamp: datetime) -> list[...
  function get_shift_for_timestamp (line 429) | def get_shift_for_timestamp(employee: str, for_timestamp: datetime) -> d...
  function get_employee_shift (line 436) | def get_employee_shift(
  function get_prev_or_next_shift (line 468) | def get_prev_or_next_shift(
  function get_employee_shift_timings (line 522) | def get_employee_shift_timings(
  function get_actual_start_end_datetime_of_shift (line 574) | def get_actual_start_end_datetime_of_shift(
  function get_exact_shift (line 592) | def get_exact_shift(shifts: list, for_timestamp: datetime) -> dict:
  function get_shift_details (line 605) | def get_shift_details(shift_type_name: str, for_timestamp: datetime | No...
  function get_shift_type (line 643) | def get_shift_type(shift_type_name: str) -> dict:
  function get_shift_timings (line 660) | def get_shift_timings(shift_type: dict, for_timestamp: datetime) -> tuple:

FILE: hrms/hr/doctype/shift_assignment/test_shift_assignment.py
  class TestShiftAssignment (line 24) | class TestShiftAssignment(HRMSTestSuite):
    method test_overlapping_for_ongoing_shift (line 25) | def test_overlapping_for_ongoing_shift(self):
    method test_multiple_shift_assignments_for_same_date (line 41) | def test_multiple_shift_assignments_for_same_date(self):
    method test_overlapping_for_fixed_period_shift (line 56) | def test_overlapping_for_fixed_period_shift(self):
    method test_overlapping_for_a_fixed_period_shift_and_ongoing_shift (line 70) | def test_overlapping_for_a_fixed_period_shift_and_ongoing_shift(self):
    method test_overlap_for_shifts_on_same_day_with_overlapping_timeslots (line 87) | def test_overlap_for_shifts_on_same_day_with_overlapping_timeslots(self):
    method test_overlap_for_midnight_shifts (line 110) | def test_overlap_for_midnight_shifts(self):
    method test_calendar (line 149) | def test_calendar(self):
    method test_calendar_for_night_shift (line 177) | def test_calendar_for_night_shift(self):
    method test_consecutive_day_and_night_shifts (line 188) | def test_consecutive_day_and_night_shifts(self):
    method test_shift_details_on_consecutive_days_with_overlapping_timings (line 218) | def test_shift_details_on_consecutive_days_with_overlapping_timings(se...
    method test_auto_attendance_calculates_ot_for_default_shift (line 251) | def test_auto_attendance_calculates_ot_for_default_shift(self):

FILE: hrms/hr/doctype/shift_assignment_tool/shift_assignment_tool.js
  method setup (line 5) | setup(frm) {
  method refresh (line 9) | refresh(frm) {
  method action (line 32) | action(frm) {
  method company (line 37) | company(frm) {
  method shift_type (line 41) | shift_type(frm) {
  method status (line 45) | status(frm) {
  method start_date (line 49) | start_date(frm) {
  method end_date (line 54) | end_date(frm) {
  method shift_type_filter (line 59) | shift_type_filter(frm) {
  method shift_schedule (line 63) | shift_schedule(frm) {
  method approver (line 67) | approver(frm) {
  method from_date (line 71) | from_date(frm) {
  method to_date (line 76) | to_date(frm) {
  method branch (line 81) | branch(frm) {
  method department (line 85) | department(frm) {
  method designation (line 89) | designation(frm) {
  method grade (line 93) | grade(frm) {
  method employment_type (line 97) | employment_type(frm) {
  method set_primary_action (line 101) | set_primary_action(frm) {
  method get_employees (line 138) | get_employees(frm) {
  method render_employees_datatable (line 155) | render_employees_datatable(frm, employees) {
  method get_assign_shift_datatable_columns (line 179) | get_assign_shift_datatable_columns() {
  method get_process_shift_requests_datatable_columns (line 215) | get_process_shift_requests_datatable_columns() {
  method bulk_assign (line 251) | bulk_assign(frm, employees) {
  method process_shift_requests (line 276) | process_shift_requests(frm, status) {
  method bulk_process_shift_requests (line 296) | bulk_process_shift_requests(frm, shift_requests, status) {

FILE: hrms/hr/doctype/shift_assignment_tool/shift_assignment_tool.py
  class ShiftAssignmentTool (line 18) | class ShiftAssignmentTool(Document):
    method get_employees (line 47) | def get_employees(self, advanced_filters: list | None = None) -> list:
    method get_employees_for_assigning_shift (line 66) | def get_employees_for_assigning_shift(self, filters):
    method get_shift_requests (line 102) | def get_shift_requests(self, filters):
    method get_query_for_employees_with_shifts (line 140) | def get_query_for_employees_with_shifts(self):
    method get_query_for_employees_with_same_shift_schedule (line 162) | def get_query_for_employees_with_same_shift_schedule(self):
    method get_query_checking_overlapping_shift_timings (line 186) | def get_query_checking_overlapping_shift_timings(self, query, doctype,...
    method bulk_assign (line 206) | def bulk_assign(self, employees: list):
    method _bulk_assign (line 232) | def _bulk_assign(self, employees: list):
    method bulk_process_shift_requests (line 284) | def bulk_process_shift_requests(self, shift_requests: list, status: str):
    method _bulk_process_shift_requests (line 303) | def _bulk_process_shift_requests(self, shift_requests: list, status: s...
    method create_shift_schedule_assignment (line 336) | def create_shift_schedule_assignment(self, employee: str) -> str:
  function create_shift_assignment (line 350) | def create_shift_assignment(

FILE: hrms/hr/doctype/shift_assignment_tool/test_shift_assignment_tool.py
  class TestShiftAssignmentTool (line 17) | class TestShiftAssignmentTool(HRMSTestSuite):
    method setUp (line 18) | def setUp(self):
    method test_get_employees_for_assigning_shifts (line 35) | def test_get_employees_for_assigning_shifts(self):
    method test_get_employees_for_assigning_shift_schedule (line 78) | def test_get_employees_for_assigning_shift_schedule(self):
    method test_get_shift_requests (line 110) | def test_get_shift_requests(self):
    method test_bulk_assign_shift (line 186) | def test_bulk_assign_shift(self):
    method test_bulk_assign_shift_schedule (line 217) | def test_bulk_assign_shift_schedule(self):
    method test_bulk_process_shift_requests (line 246) | def test_bulk_process_shift_requests(self):
  function make_shift_schedule_assignment (line 302) | def make_shift_schedule_assignment(schedule, employee, create_shifts_aft...

FILE: hrms/hr/doctype/shift_location/shift_location.py
  class ShiftLocation (line 10) | class ShiftLocation(Document):
    method validate (line 25) | def validate(self):
    method set_geolocation (line 29) | def set_geolocation(self):

FILE: hrms/hr/doctype/shift_location/test_shift_location.py
  class TestShiftLocation (line 8) | class TestShiftLocation(HRMSTestSuite):

FILE: hrms/hr/doctype/shift_request/shift_request.py
  class OverlappingShiftRequestError (line 16) | class OverlappingShiftRequestError(frappe.ValidationError):
  class ShiftRequest (line 20) | class ShiftRequest(Document, PWANotificationsMixin):
    method validate (line 41) | def validate(self):
    method on_update (line 48) | def on_update(self):
    method after_delete (line 53) | def after_delete(self):
    method publish_update (line 56) | def publish_update(self):
    method after_insert (line 61) | def after_insert(self):
    method on_submit (line 64) | def on_submit(self):
    method on_cancel (line 86) | def on_cancel(self):
    method on_discard (line 95) | def on_discard(self):
    method validate_default_shift (line 98) | def validate_default_shift(self):
    method validate_approver (line 105) | def validate_approver(self):
    method validate_overlapping_shift_requests (line 117) | def validate_overlapping_shift_requests(self):
    method get_overlapping_dates (line 125) | def get_overlapping_dates(self):
    method throw_overlap_error (line 146) | def throw_overlap_error(self, shift_details):

FILE: hrms/hr/doctype/shift_request/shift_request_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/shift_request/test_shift_request.py
  class TestShiftRequest (line 14) | class TestShiftRequest(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_make_shift_request (line 19) | def test_make_shift_request(self):
    method test_shift_request_approver_perms (line 49) | def test_shift_request_approver_perms(self):
    method test_overlap_for_request_without_to_date (line 90) | def test_overlap_for_request_without_to_date(self):
    method test_overlap_for_request_with_from_and_to_dates (line 122) | def test_overlap_for_request_with_from_and_to_dates(self):
    method test_overlapping_for_a_fixed_period_shift_and_ongoing_shift (line 155) | def test_overlapping_for_a_fixed_period_shift_and_ongoing_shift(self):
    method test_allow_non_overlapping_shift_requests_for_same_day (line 194) | def test_allow_non_overlapping_shift_requests_for_same_day(self):
    method test_status_on_discard (line 230) | def test_status_on_discard(self):
  function set_shift_approver (line 247) | def set_shift_approver(department):
  function make_shift_request (line 254) | def make_shift_request(

FILE: hrms/hr/doctype/shift_schedule/shift_schedule.js
  method refresh (line 5) | refresh(frm) {

FILE: hrms/hr/doctype/shift_schedule/shift_schedule.py
  class ShiftSchedule (line 9) | class ShiftSchedule(Document):
    method before_validate (line 25) | def before_validate(self):
  function get_or_insert_shift_schedule (line 39) | def get_or_insert_shift_schedule(shift_type: str, frequency: str, repeat...

FILE: hrms/hr/doctype/shift_schedule_assignment/shift_schedule_assignment.py
  class ShiftScheduleAssignment (line 12) | class ShiftScheduleAssignment(Document):
    method validate (line 31) | def validate(self):
    method validate_existing_shift_assignments (line 34) | def validate_existing_shift_assignments(self):
    method get_existing_shift_assignments (line 58) | def get_existing_shift_assignments(self):
    method create_shifts (line 82) | def create_shifts(self, start_date: str, end_date: str | None = None) ...
    method create_individual_assignment (line 125) | def create_individual_assignment(self, shift_type, start_date, end_date):
  function process_auto_shift_creation (line 139) | def process_auto_shift_creation():

FILE: hrms/hr/doctype/shift_schedule_assignment/test_shift_schedule_assignment.py
  class TestShiftScheduleAssignment (line 14) | class TestShiftScheduleAssignment(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_existing_shift_assignment_validation (line 24) | def test_existing_shift_assignment_validation(self):

FILE: hrms/hr/doctype/shift_type/shift_type.py
  class ShiftType (line 38) | class ShiftType(Document):
    method validate (line 77) | def validate(self):
    method validate_same_start_and_end (line 84) | def validate_same_start_and_end(self, start_time: datetime.time, end_t...
    method validate_circular_shift (line 91) | def validate_circular_shift(self, start_time: datetime.time, end_time:...
    method get_shift_start_and_shift_end (line 102) | def get_shift_start_and_shift_end(
    method get_total_shift_duration_in_minutes (line 112) | def get_total_shift_duration_in_minutes(
    method get_max_shift_buffer_label (line 121) | def get_max_shift_buffer_label(self) -> str:
    method validate_unlinked_logs (line 132) | def validate_unlinked_logs(self):
    method is_field_modified (line 139) | def is_field_modified(self, fieldname):
    method unlinked_checkins_exist (line 142) | def unlinked_checkins_exist(self):
    method process_auto_attendance (line 149) | def process_auto_attendance(self, is_manually_triggered: int | bool = ...
    method has_incorrect_shift_config (line 169) | def has_incorrect_shift_config(self):
    method _process (line 176) | def _process(self, logs):
    method is_half_holiday (line 233) | def is_half_holiday(self, employee, attendance_date):
    method get_employee_checkins (line 239) | def get_employee_checkins(self) -> list[dict]:
    method get_attendance (line 266) | def get_attendance(self, logs, working_hours_threshold_for_absent, wor...
    method mark_absent_for_dates_with_no_attendance (line 302) | def mark_absent_for_dates_with_no_attendance(self, employee: str):
    method get_dates_for_attendance (line 329) | def get_dates_for_attendance(self, employee: str) -> list[str]:
    method get_start_and_end_dates (line 346) | def get_start_and_end_dates(self, employee):
    method get_marked_attendance_dates_between (line 380) | def get_marked_attendance_dates_between(self, employee: str, start_dat...
    method get_assigned_employees (line 393) | def get_assigned_employees(self, from_date: datetime.date, consider_de...
    method get_holiday_list (line 416) | def get_holiday_list(self, employee: str, date=None) -> str:
    method should_mark_attendance (line 420) | def should_mark_attendance(self, employee: str, attendance_date: str) ...
    method mark_absent_for_half_day_dates (line 432) | def mark_absent_for_half_day_dates(self, employee):
  function update_last_sync_of_checkin (line 466) | def update_last_sync_of_checkin():
  function get_actual_shift_end (line 488) | def get_actual_shift_end(shift, current_datetime):
  function process_auto_attendance_for_all_shifts (line 500) | def process_auto_attendance_for_all_shifts():

FILE: hrms/hr/doctype/shift_type/shift_type_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/shift_type/test_shift_type.py
  class TestShiftType (line 26) | class TestShiftType(HRMSTestSuite):
    method setUp (line 27) | def setUp(self):
    method test_auto_update_last_sync_of_checkin_for_single_day_shift (line 33) | def test_auto_update_last_sync_of_checkin_for_single_day_shift(self):
    method test_auto_update_last_sync_of_checkin_for_shifts_spanning_two_days_due_to_buffer (line 56) | def test_auto_update_last_sync_of_checkin_for_shifts_spanning_two_days...
    method test_auto_update_last_sync_of_checkin_for_two_day_shift (line 84) | def test_auto_update_last_sync_of_checkin_for_two_day_shift(self):
    method test_auto_update_last_sync_of_checkin_when_when_job_runs_on_the_next_day (line 109) | def test_auto_update_last_sync_of_checkin_when_when_job_runs_on_the_ne...
    method test_mark_attendance (line 123) | def test_mark_attendance(self):
    method test_mark_attendance_with_different_shift_start_time (line 147) | def test_mark_attendance_with_different_shift_start_time(self):
    method test_attendance_date_for_different_start_and_actual_start_date (line 176) | def test_attendance_date_for_different_start_and_actual_start_date(self):
    method test_entry_and_exit_grace (line 206) | def test_entry_and_exit_grace(self):
    method test_working_hours_threshold_for_half_day (line 242) | def test_working_hours_threshold_for_half_day(self):
    method test_working_hours_threshold_for_absent (line 266) | def test_working_hours_threshold_for_absent(self):
    method test_working_hours_threshold_for_absent_and_half_day_1 (line 290) | def test_working_hours_threshold_for_absent_and_half_day_1(self):
    method test_working_hours_threshold_for_absent_and_half_day_2 (line 319) | def test_working_hours_threshold_for_absent_and_half_day_2(self):
    method test_mark_auto_attendance_on_holiday_enabled (line 346) | def test_mark_auto_attendance_on_holiday_enabled(self):
    method test_mark_auto_attendance_on_holiday_disabled (line 375) | def test_mark_auto_attendance_on_holiday_disabled(self):
    method test_mark_absent_for_dates_with_no_attendance (line 403) | def test_mark_absent_for_dates_with_no_attendance(self):
    method test_mark_absent_for_dates_with_no_attendance_for_midnight_shift (line 438) | def test_mark_absent_for_dates_with_no_attendance_for_midnight_shift(s...
    method test_do_not_mark_absent_before_shift_actual_end_time (line 523) | def test_do_not_mark_absent_before_shift_actual_end_time(self):
    method test_do_not_mark_absent_before_shift_actual_end_time_for_midnight_shift (line 546) | def test_do_not_mark_absent_before_shift_actual_end_time_for_midnight_...
    method test_skip_marking_absent_on_a_holiday (line 590) | def test_skip_marking_absent_on_a_holiday(self):
    method test_skip_absent_marking_for_a_fallback_default_shift (line 612) | def test_skip_absent_marking_for_a_fallback_default_shift(self):
    method test_skip_absent_marking_for_inactive_employee (line 651) | def test_skip_absent_marking_for_inactive_employee(self):
    method test_get_start_and_end_dates (line 666) | def test_get_start_and_end_dates(self):
    method test_skip_auto_attendance_for_duplicate_record (line 701) | def test_skip_auto_attendance_for_duplicate_record(self):
    method test_skip_auto_attendance_for_overlapping_shift (line 731) | def test_skip_auto_attendance_for_overlapping_shift(self):
    method test_mark_attendance_for_default_shift_when_shift_assignment_is_not_overlapping (line 765) | def test_mark_attendance_for_default_shift_when_shift_assignment_is_no...
    method test_validation_for_unlinked_logs_before_changing_important_shift_configuration (line 786) | def test_validation_for_unlinked_logs_before_changing_important_shift_...
    method test_circular_shift_times (line 822) | def test_circular_shift_times(self):
    method test_bg_job_creation_for_large_checkins (line 860) | def test_bg_job_creation_for_large_checkins(self):
    method test_precision_for_working_hours_threshold (line 867) | def test_precision_for_working_hours_threshold(self):
    method test_working_hours_threshold_for_half_day_holiday (line 913) | def test_working_hours_threshold_for_half_day_holiday(self):
  function setup_shift_type (line 966) | def setup_shift_type(**args):
  function make_shift_assignment (line 1010) | def make_shift_assignment(

FILE: hrms/hr/doctype/skill/skill.py
  class Skill (line 9) | class Skill(Document):

FILE: hrms/hr/doctype/skill_assessment/skill_assessment.py
  class SkillAssessment (line 9) | class SkillAssessment(Document):

FILE: hrms/hr/doctype/staffing_plan/staffing_plan.js
  method get_query (line 38) | get_query() {
  method action (line 50) | action(selections) {

FILE: hrms/hr/doctype/staffing_plan/staffing_plan.py
  class SubsidiaryCompanyError (line 13) | class SubsidiaryCompanyError(frappe.ValidationError):
  class ParentCompanyError (line 17) | class ParentCompanyError(frappe.ValidationError):
  class StaffingPlan (line 21) | class StaffingPlan(Document):
    method validate (line 41) | def validate(self):
    method validate_period (line 46) | def validate_period(self):
    method validate_details (line 51) | def validate_details(self):
    method set_total_estimated_budget (line 57) | def set_total_estimated_budget(self):
    method set_number_of_positions (line 75) | def set_number_of_positions(self, detail):
    method validate_overlap (line 78) | def validate_overlap(self, staffing_plan_detail):
    method validate_with_parent_plan (line 96) | def validate_with_parent_plan(self, staffing_plan_detail):
    method validate_with_subsidiary_plans (line 161) | def validate_with_subsidiary_plans(self, staffing_plan_detail):
    method set_job_requisitions (line 193) | def set_job_requisitions(self, job_reqs: list[str]) -> Document:
  function get_designation_counts (line 218) | def get_designation_counts(designation: str, company: str, job_opening: ...
  function get_active_staffing_plan_details (line 239) | def get_active_staffing_plan_details(

FILE: hrms/hr/doctype/staffing_plan/staffing_plan_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/staffing_plan/test_staffing_plan.py
  class TestStaffingPlan (line 13) | class TestStaffingPlan(HRMSTestSuite):
    method setUp (line 14) | def setUp(self):
    method test_staffing_plan (line 17) | def test_staffing_plan(self):
    method test_staffing_plan_subsidiary_company (line 34) | def test_staffing_plan_subsidiary_company(self):
    method test_staffing_plan_parent_company (line 49) | def test_staffing_plan_parent_company(self):
    method test_staffing_details_from_job_requisition (line 78) | def test_staffing_details_from_job_requisition(self):
  function make_company (line 106) | def make_company(name=None, abbr=None):

FILE: hrms/hr/doctype/staffing_plan_detail/staffing_plan_detail.py
  class StaffingPlanDetail (line 8) | class StaffingPlanDetail(Document):

FILE: hrms/hr/doctype/training_event/test_training_event.py
  class TestTrainingEvent (line 11) | class TestTrainingEvent(HRMSTestSuite):
    method setUp (line 12) | def setUp(self):
    method test_training_event_status_update (line 18) | def test_training_event_status_update(self):
  function create_training_program (line 37) | def create_training_program(training_program, company="_Test Company"):
  function create_training_event (line 49) | def create_training_event(attendees):

FILE: hrms/hr/doctype/training_event/training_event.py
  class TrainingEvent (line 13) | class TrainingEvent(Document):
    method validate (line 45) | def validate(self):
    method on_update_after_submit (line 49) | def on_update_after_submit(self):
    method set_employee_emails (line 52) | def set_employee_emails(self):
    method validate_period (line 55) | def validate_period(self):
    method set_status_for_attendees (line 59) | def set_status_for_attendees(self):

FILE: hrms/hr/doctype/training_event/training_event_dashboard.py
  function get_data (line 1) | def get_data():

FILE: hrms/hr/doctype/training_event_employee/training_event_employee.py
  class TrainingEventEmployee (line 8) | class TrainingEventEmployee(Document):

FILE: hrms/hr/doctype/training_feedback/test_training_feedback.py
  class TestTrainingFeedback (line 14) | class TestTrainingFeedback(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_employee_validations_for_feedback (line 21) | def test_employee_validations_for_feedback(self):
    method test_training_feedback_status (line 42) | def test_training_feedback_status(self):
  function create_training_feedback (line 60) | def create_training_feedback(event, employee):

FILE: hrms/hr/doctype/training_feedback/training_feedback.py
  class TrainingFeedback (line 10) | class TrainingFeedback(Document):
    method validate (line 30) | def validate(self):
    method on_submit (line 52) | def on_submit(self):
    method on_cancel (line 60) | def on_cancel(self):

FILE: hrms/hr/doctype/training_program/test_training_program.py
  class TestTrainingProgram (line 7) | class TestTrainingProgram(HRMSTestSuite):

FILE: hrms/hr/doctype/training_program/training_program.py
  class TrainingProgram (line 8) | class TrainingProgram(Document):

FILE: hrms/hr/doctype/training_program/training_program_dashboard.py
  function get_data (line 4) | def get_data():

FILE: hrms/hr/doctype/training_result/test_training_result.py
  class TestTrainingResult (line 9) | class TestTrainingResult(HRMSTestSuite):

FILE: hrms/hr/doctype/training_result/training_result.py
  class TrainingResult (line 12) | class TrainingResult(Document):
    method validate (line 29) | def validate(self):
    method on_submit (line 36) | def on_submit(self):
  function get_employees (line 49) | def get_employees(training_event: str):

FILE: hrms/hr/doctype/training_result_employee/training_result_employee.py
  class TrainingResultEmployee (line 8) | class TrainingResultEmployee(Document):

FILE: hrms/hr/doctype/travel_itinerary/travel_itinerary.py
  class TravelItinerary (line 8) | class TravelItinerary(Document):

FILE: hrms/hr/doctype/travel_request/test_travel_request.py
  class TestTravelRequest (line 7) | class TestTravelRequest(HRMSTestSuite):

FILE: hrms/hr/doctype/travel_request/travel_request.py
  class TravelRequest (line 10) | class TravelRequest(Document):
    method validate (line 48) | def validate(self):

FILE: hrms/hr/doctype/travel_request_costing/travel_request_costing.py
  class TravelRequestCosting (line 8) | class TravelRequestCosting(Document):

FILE: hrms/hr/doctype/upload_attendance/test_upload_attendance.py
  class TestUploadAttendance (line 14) | class TestUploadAttendance(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_date_range (line 18) | def test_date_range(self):

FILE: hrms/hr/doctype/upload_attendance/upload_attendance.js
  method onload (line 7) | onload() {
  method refresh (line 12) | refresh() {
  method get_template (line 18) | get_template() {
  method show_upload (line 33) | show_upload() {
  method setup_import_progress (line 42) | setup_import_progress() {

FILE: hrms/hr/doctype/upload_attendance/upload_attendance.py
  class UploadAttendance (line 18) | class UploadAttendance(Document):
  function get_template (line 35) | def get_template():
  function add_header (line 60) | def add_header(w):
  function add_data (line 72) | def add_data(w, args):
  function get_data (line 78) | def get_data(args):
  function get_holidays_for_employees (line 121) | def get_holidays_for_employees(employees, from_date, to_date):
  function writedata (line 132) | def writedata(w, data):
  function get_dates (line 137) | def get_dates(args):
  function get_active_employees (line 144) | def get_active_employees():
  function get_existing_attendance_records (line 153) | def get_existing_attendance_records(args):
  function get_naming_series (line 168) | def get_naming_series():
  function upload (line 176) | def upload():
  function import_attendances (line 188) | def import_attendances(rows):

FILE: hrms/hr/doctype/vehicle_log/test_vehicle_log.py
  class TestVehicleLog (line 13) | class TestVehicleLog(HRMSTestSuite):
    method setUp (line 14) | def setUp(self):
    method test_make_vehicle_log_and_syncing_of_odometer_value (line 23) | def test_make_vehicle_log_and_syncing_of_odometer_value(self):
    method test_vehicle_log_fuel_expense (line 42) | def test_vehicle_log_fuel_expense(self):
    method test_vehicle_log_with_service_expenses (line 53) | def test_vehicle_log_with_service_expenses(self):
  function get_vehicle (line 65) | def get_vehicle(employee_id):
  function make_vehicle_log (line 89) | def make_vehicle_log(license_plate, employee_id, with_services=False):

FILE: hrms/hr/doctype/vehicle_log/vehicle_log.py
  class VehicleLog (line 11) | class VehicleLog(Document):
    method validate (line 38) | def validate(self):
    method on_submit (line 46) | def on_submit(self):
    method on_cancel (line 49) | def on_cancel(self):
  function make_expense_claim (line 59) | def make_expense_claim(docname: str) -> dict:

FILE: hrms/hr/doctype/vehicle_service/vehicle_service.py
  class VehicleService (line 8) | class VehicleService(Document):

FILE: hrms/hr/doctype/vehicle_service_item/test_vehicle_service_item.py
  class TestVehicleServiceItem (line 8) | class TestVehicleServiceItem(HRMSTestSuite):

FILE: hrms/hr/doctype/vehicle_service_item/vehicle_service_item.py
  class VehicleServiceItem (line 8) | class VehicleServiceItem(Document):

FILE: hrms/hr/notification/exit_interview_scheduled/exit_interview_scheduled.py
  function get_context (line 4) | def get_context(context):

FILE: hrms/hr/notification/training_feedback/training_feedback.py
  function get_context (line 1) | def get_context(context):

FILE: hrms/hr/notification/training_scheduled/training_scheduled.py
  function get_context (line 1) | def get_context(context):

FILE: hrms/hr/page/organizational_chart/organizational_chart.py
  function get_children (line 6) | def get_children(parent=None, company=None, exclude_node=None):
  function get_connections (line 41) | def get_connections(employee: str, lft: int, rgt: int) -> int:

FILE: hrms/hr/page/organizational_chart/test_organizational_chart.py
  class TestOrganizationalChart (line 13) | class TestOrganizationalChart(HRMSTestSuite):
    method setUp (line 14) | def setUp(self):
    method test_get_children (line 18) | def test_get_children(self):

FILE: hrms/hr/page/team_updates/team_updates.py
  function get_data (line 7) | def get_data(start=0):

FILE: hrms/hr/report/appraisal_overview/appraisal_overview.py
  function execute (line 8) | def execute(filters: dict | None = None) -> tuple:
  function get_columns (line 17) | def get_columns() -> list[dict]:
  function get_data (line 68) | def get_data(filters: dict | None = None) -> list[dict]:
  function get_chart_data (line 103) | def get_chart_data(data: list[dict]) -> dict:

FILE: hrms/hr/report/appraisal_overview/test_appraisal_overview.py
  class TestAppraisalOverview (line 16) | class TestAppraisalOverview(HRMSTestSuite):
    method setUp (line 17) | def setUp(self):
    method test_appraisal_overview (line 34) | def test_appraisal_overview(self):
    method test_appraisal_filters (line 62) | def test_appraisal_filters(self):
    method create_appraisal_data (line 72) | def create_appraisal_data(self, appraisal):

FILE: hrms/hr/report/daily_work_summary_replies/daily_work_summary_replies.py
  function execute (line 11) | def execute(filters=None):
  function get_columns (line 18) | def get_columns(filters=None):
  function get_data (line 39) | def get_data(filters):

FILE: hrms/hr/report/employee_advance_summary/employee_advance_summary.py
  function execute (line 11) | def execute(filters=None):
  function get_columns (line 41) | def get_columns():
  function get_advances (line 105) | def get_advances(filters):

FILE: hrms/hr/report/employee_analytics/employee_analytics.py
  function execute (line 13) | def execute(filters=None):
  function get_columns (line 28) | def get_columns():
  function get_employees (line 41) | def get_employees(filters):
  function get_parameters (line 63) | def get_parameters(filters):
  function get_chart_data (line 71) | def get_chart_data(parameters, filters):

FILE: hrms/hr/report/employee_analytics/test_employee_analytics.py
  class TestEmployeeAnalytics (line 11) | class TestEmployeeAnalytics(HRMSTestSuite):
    method setUp (line 12) | def setUp(self):
    method test_branches (line 18) | def test_branches(self):
    method test_employee_grade (line 37) | def test_employee_grade(self):
  function test_data (line 51) | def test_data(self, values_to_assert, chart_data):
  function create_employee_grade (line 60) | def create_employee_grade():
  function create_branches (line 68) | def create_branches():
  function get_employees_without_set_parameter (line 76) | def get_employees_without_set_parameter(parameter, company):
  function create_company (line 80) | def create_company(company_name):

FILE: hrms/hr/report/employee_birthday/employee_birthday.py
  function execute (line 13) | def execute(filters=None):
  function get_columns (line 24) | def get_columns():
  function get_employees (line 37) | def get_employees(filters):
  function get_filtered_month (line 62) | def get_filtered_month(filters):

FILE: hrms/hr/report/employee_birthday/test_employee_birthday.py
  class TestEmployeeBirthday (line 11) | class TestEmployeeBirthday(HRMSTestSuite):
    method setUp (line 12) | def setUp(self):
    method test_employee_birth_day_report (line 17) | def test_employee_birth_day_report(self):
    method test_user_permissions_on_employees (line 40) | def test_user_permissions_on_employees(self):

FILE: hrms/hr/report/employee_exits/employee_exits.py
  function execute (line 12) | def execute(filters=None):
  function get_columns (line 21) | def get_columns():
  function get_data (line 88) | def get_data(filters):
  function get_conditions (line 128) | def get_conditions(filters, query, employee, interview, fnf):
  function get_chart_data (line 175) | def get_chart_data(data):
  function get_report_summary (line 203) | def get_report_summary(data):

FILE: hrms/hr/report/employee_exits/test_employee_exits.py
  class TestEmployeeExits (line 15) | class TestEmployeeExits(HRMSTestSuite):
    method setUp (line 16) | def setUp(self):
    method create_records (line 20) | def create_records(self):
    method test_employee_exits_summary (line 81) | def test_employee_exits_summary(self):
    method test_pending_exit_interviews_summary (line 126) | def test_pending_exit_interviews_summary(self):
    method test_pending_exit_questionnaire_summary (line 156) | def test_pending_exit_questionnaire_summary(self):
    method test_pending_fnf_summary (line 186) | def test_pending_fnf_summary(self):

FILE: hrms/hr/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py
  function execute (line 10) | def execute(filters=None):
  class EmployeeHoursReport (line 14) | class EmployeeHoursReport:
    method __init__ (line 17) | def __init__(self, filters=None):
    method validate_dates (line 26) | def validate_dates(self):
    method validate_standard_working_hours (line 32) | def validate_standard_working_hours(self):
    method run (line 42) | def run(self):
    method generate_columns (line 50) | def generate_columns(self):
    method generate_data (line 99) | def generate_data(self):
    method filter_stats_by_department (line 120) | def filter_stats_by_department(self):
    method generate_filtered_time_logs (line 129) | def generate_filtered_time_logs(self):
    method generate_stats_by_employee (line 155) | def generate_stats_by_employee(self):
    method set_employee_department_and_name (line 168) | def set_employee_department_and_name(self):
    method calculate_utilizations (line 176) | def calculate_utilizations(self):
    method generate_report_summary (line 189) | def generate_report_summary(self):
    method generate_chart_data (line 231) | def generate_chart_data(self):

FILE: hrms/hr/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py
  class TestEmployeeUtilization (line 13) | class TestEmployeeUtilization(HRMSTestSuite):
    method setUp (line 14) | def setUp(self):
    method create_test_timesheets (line 27) | def create_test_timesheets(self):
    method test_utilization_report_with_required_filters_only (line 65) | def test_utilization_report_with_required_filters_only(self):
    method test_utilization_report_for_single_employee (line 73) | def test_utilization_report_for_single_employee(self):
    method test_utilization_report_for_project (line 100) | def test_utilization_report_for_project(self):
    method test_utilization_report_for_department (line 127) | def test_utilization_report_for_department(self):
    method test_report_summary_data (line 141) | def test_report_summary_data(self):
    method get_expected_data_for_test_employees (line 153) | def get_expected_data_for_test_employees(self):

FILE: hrms/hr/report/employee_leave_balance/employee_leave_balance.py
  function execute (line 21) | def execute(filters: Filters | None = None) -> tuple:
  function get_columns (line 31) | def get_columns() -> list[dict]:
  function get_data (line 87) | def get_data(filters: Filters) -> list:
  function get_leave_types (line 134) | def get_leave_types() -> list[str]:
  function get_employees (line 139) | def get_employees(filters: Filters) -> list[dict]:
  function get_opening_balance (line 160) | def get_opening_balance(
  function get_allocated_and_expired_leaves (line 184) | def get_allocated_and_expired_leaves(
  function get_allocated_leaves (line 198) | def get_allocated_leaves(from_date, to_date, employee, leave_type):
  function get_expired_leaves (line 215) | def get_expired_leaves(from_date, to_date, employee, leave_type):
  function get_cf_leaves (line 232) | def get_cf_leaves(from_date, to_date, employee, leave_type):
  function get_chart_data (line 249) | def get_chart_data(data: list, filters: Filters) -> dict:
  function get_dataset_for_chart (line 269) | def get_dataset_for_chart(employee_data: list, datasets: list, labels: l...

FILE: hrms/hr/report/employee_leave_balance/test_employee_leave_balance.py
  class TestEmployeeLeaveBalance (line 28) | class TestEmployeeLeaveBalance(HRMSTestSuite):
    method setUp (line 29) | def setUp(self):
    method test_employee_leave_balance (line 53) | def test_employee_leave_balance(self):
    method test_opening_balance_on_alloc_boundary_dates (line 104) | def test_opening_balance_on_alloc_boundary_dates(self):
    method test_opening_balance_considers_carry_forwarded_leaves (line 155) | def test_opening_balance_considers_carry_forwarded_leaves(self):
    method test_employee_status_filter (line 209) | def test_employee_status_filter(self):
    method test_manually_expired_leaves (line 245) | def test_manually_expired_leaves(self):

FILE: hrms/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
  function execute (line 11) | def execute(filters=None):
  function get_columns (line 20) | def get_columns(leave_types):
  function get_conditions (line 33) | def get_conditions(filters):
  function get_data (line 47) | def get_data(filters, leave_types):

FILE: hrms/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py
  class TestEmployeeLeaveBalance (line 24) | class TestEmployeeLeaveBalance(HRMSTestSuite):
    method setUp (line 25) | def setUp(self):
    method test_employee_leave_balance_summary (line 48) | def test_employee_leave_balance_summary(self):
    method test_get_leave_balance_near_alloc_expiry (line 112) | def test_get_leave_balance_near_alloc_expiry(self):
    method test_employee_status_filter (line 145) | def test_employee_status_filter(self):

FILE: hrms/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py
  function execute (line 11) | def execute(filters=None):
  function get_columns (line 20) | def get_columns():
  function get_data (line 53) | def get_data(filters):

FILE: hrms/hr/report/employees_working_on_a_holiday/test_employees_working_on_a_holiday.py
  class TestEmployeesWorkingOnAHoliday (line 18) | class TestEmployeesWorkingOnAHoliday(HRMSTestSuite):
    method setUp (line 19) | def setUp(self):
    method test_report (line 23) | def test_report(self):

FILE: hrms/hr/report/leave_ledger/leave_ledger.py
  function execute (line 11) | def execute(filters: Filters = None) -> tuple:
  function get_columns (line 18) | def get_columns() -> list[dict]:
  function get_data (line 119) | def get_data(filters: Filters) -> list[dict]:
  function add_total_row (line 169) | def add_total_row(result: list[dict], filters: Filters) -> list[dict]:

FILE: hrms/hr/report/leave_ledger/test_leave_ledger.py
  class TestLeaveLedger (line 22) | class TestLeaveLedger(HRMSTestSuite):
    method setUp (line 23) | def setUp(self):
    method create_earned_leave_allocation (line 63) | def create_earned_leave_allocation(self):
    method create_casual_leave_allocation (line 83) | def create_casual_leave_allocation(self):
    method create_leave_applications (line 93) | def create_leave_applications(self):
    method test_report_with_filters (line 104) | def test_report_with_filters(self):
    method test_totals (line 150) | def test_totals(self):

FILE: hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js
  function set_reqd_filter (line 150) | function set_reqd_filter(fieldname, is_reqd) {
  function validate_date_range (line 155) | function validate_date_range(report) {

FILE: hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
  function execute (line 37) | def execute(filters: Filters | None = None) -> tuple:
  function get_message (line 80) | def get_message() -> str:
  function get_columns (line 105) | def get_columns(filters: Filters) -> list[dict]:
  function get_columns_for_leave_types (line 188) | def get_columns_for_leave_types() -> list[dict]:
  function get_columns_for_days (line 197) | def get_columns_for_days(filters: Filters) -> list[dict]:
  function get_dates_in_period (line 211) | def get_dates_in_period(filters: Filters) -> list[str]:
  function get_total_days_in_month (line 225) | def get_total_days_in_month(filters: Filters) -> int:
  function get_date_condition (line 229) | def get_date_condition(docfield: Field, filters: Filters) -> Criterion:
  function get_data (line 236) | def get_data(filters: Filters, attendance_map: dict) -> list[dict]:
  function get_attendance_map (line 260) | def get_attendance_map(filters: Filters) -> dict:
  function get_attendance_records (line 306) | def get_attendance_records(filters: Filters) -> list[dict]:
  function get_employee_related_details (line 343) | def get_employee_related_details(filters: Filters) -> tuple[dict, list]:
  function get_holiday_map (line 403) | def get_holiday_map(filters: Filters) -> dict[str, list[dict]]:
  function get_rows (line 442) | def get_rows(employee_details: dict, filters: Filters, holiday_map: dict...
  function set_defaults_for_summarized_view (line 484) | def set_defaults_for_summarized_view(filters, row):
  function get_attendance_status_for_summarized_view (line 490) | def get_attendance_status_for_summarized_view(
  function get_attendance_summary_and_days (line 523) | def get_attendance_summary_and_days(employee: str, filters: Filters) -> ...
  function get_attendance_status_for_detailed_view (line 575) | def get_attendance_status_for_detailed_view(
  function get_holiday_status (line 609) | def get_holiday_status(holiday_date: date, holidays: list) -> str:
  function get_leave_summary (line 622) | def get_leave_summary(employee: str, filters: Filters) -> dict[str, float]:
  function get_entry_exits_summary (line 653) | def get_entry_exits_summary(employee: str, filters: Filters) -> dict[str...
  function get_attendance_years (line 682) | def get_attendance_years() -> str:
  function get_chart_data (line 697) | def get_chart_data(attendance_map: dict, filters: Filters) -> dict:

FILE: hrms/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
  class TestMonthlyAttendanceSheet (line 22) | class TestMonthlyAttendanceSheet(HRMSTestSuite):
    method setUp (line 23) | def setUp(self):
    method test_monthly_attendance_sheet_report (line 39) | def test_monthly_attendance_sheet_report(self):
    method test_detailed_view (line 74) | def test_detailed_view(self):
    method test_single_shift_with_leaves_in_detailed_view (line 119) | def test_single_shift_with_leaves_in_detailed_view(self):
    method test_single_leave_record (line 155) | def test_single_leave_record(self):
    method test_summarized_view (line 179) | def test_summarized_view(self):
    method test_attendance_with_group_by_filter (line 225) | def test_attendance_with_group_by_filter(self):
    method test_attendance_with_employee_filter (line 275) | def test_attendance_with_employee_filter(self):
    method test_attendance_with_company_filter (line 322) | def test_attendance_with_company_filter(self):
    method test_attendance_with_employee_filter_and_summarized_view (line 352) | def test_attendance_with_employee_filter_and_summarized_view(self):
    method test_validations (line 401) | def test_validations(self):
    method test_summarised_view_with_date_range_filter (line 443) | def test_summarised_view_with_date_range_filter(self):
    method test_detailed_view_with_date_range_filter (line 488) | def test_detailed_view_with_date_range_filter(self):
    method test_detailed_view_with_date_range_and_group_by_filter (line 522) | def test_detailed_view_with_date_range_and_group_by_filter(self):
  function get_leave_application (line 558) | def get_leave_application(employee, date=None):
  function execute_report_with_invalid_filters (line 574) | def execute_report_with_invalid_filters(invalid_filter_name):
  function date_key (line 590) | def date_key(date_obj):

FILE: hrms/hr/report/project_profitability/project_profitability.py
  function execute (line 9) | def execute(filters=None):
  function get_data (line 16) | def get_data(filters):
  function get_rows (line 22) | def get_rows(filters):
  function calculate_cost_and_profit (line 79) | def calculate_cost_and_profit(data):
  function get_standard_working_hours (line 97) | def get_standard_working_hours() -> float | None:
  function get_chart_data (line 110) | def get_chart_data(data):
  function get_columns (line 129) | def get_columns():

FILE: hrms/hr/report/project_profitability/test_project_profitability.py
  class TestProjectProfitability (line 14) | class TestProjectProfitability(HRMSTestSuite):
    method setUp (line 15) | def setUp(self):
    method test_project_profitability (line 40) | def test_project_profitability(self):
  function create_activity_type (line 73) | def create_activity_type(activity_type: str) -> str:

FILE: hrms/hr/report/recruitment_analytics/recruitment_analytics.py
  function execute (line 9) | def execute(filters=None):
  function get_columns (line 21) | def get_columns():
  function get_data (line 69) | def get_data(filters):
  function get_parent_row (line 84) | def get_parent_row(sp_jo_map, sp, jo_ja_map, ja_joff_map):
  function get_child_row (line 98) | def get_child_row(jo, jo_ja_map, ja_joff_map):
  function get_staffing_plan (line 119) | def get_staffing_plan(filters):
  function get_job_opening (line 146) | def get_job_opening(sp_list, filters):
  function get_job_applicant (line 167) | def get_job_applicant(jo_list):
  function get_job_offer (line 188) | def get_job_offer(ja_list, filters=None):

FILE: hrms/hr/report/shift_attendance/shift_attendance.py
  function execute (line 14) | def execute(filters=None):
  function get_columns (line 22) | def get_columns():
  function get_data (line 135) | def get_data(filters):
  function get_report_summary (line 143) | def get_report_summary(data):
  function get_chart_data (line 196) | def get_chart_data(data):
  function get_attendance_with_checkins (line 216) | def get_attendance_with_checkins(filters):
  function get_base_attendance_query (line 245) | def get_base_attendance_query(filters):
  function get_attendance_without_checkins (line 286) | def get_attendance_without_checkins(filters):
  function update_data (line 300) | def update_data(data, filters):
  function format_float_precision (line 314) | def format_float_precision(value):
  function format_in_out_time (line 319) | def format_in_out_time(in_time, out_time, attendance_date):
  function convert_datetime_to_time_for_same_date (line 329) | def convert_datetime_to_time_for_same_date(start, end):
  function update_late_entry (line 339) | def update_late_entry(entry, consider_grace_period):
  function update_early_exit (line 352) | def update_early_exit(entry, consider_grace_period):

FILE: hrms/hr/report/shift_attendance/test_shift_attendance.py
  class TestShiftAttendance (line 15) | class TestShiftAttendance(HRMSTestSuite):
    method setUp (line 16) | def setUp(self):
    method create_records (line 20) | def create_records(self):
    method test_data (line 77) | def test_data(self):
    method test_chart (line 135) | def test_chart(self):
    method test_report_summary (line 150) | def test_report_summary(self):
    method test_user_permission_on_attendance_records (line 167) | def test_user_permission_on_attendance_records(self):
    method test_get_attendance_records_without_checkins (line 219) | def test_get_attendance_records_without_checkins(self):
  function get_chart_data (line 261) | def get_chart_data(report):
  function make_checkin (line 272) | def make_checkin(employee, time, log_type):

FILE: hrms/hr/report/unpaid_expense_claim/unpaid_expense_claim.py
  function execute (line 10) | def execute(filters=None):
  function get_columns (line 17) | def get_columns():
  function get_unclaimed_expese_claims (line 28) | def get_unclaimed_expese_claims(filters):

FILE: hrms/hr/report/vehicle_expenses/test_vehicle_expenses.py
  class TestVehicleExpenses (line 17) | class TestVehicleExpenses(HRMSTestSuite):
    method setUp (line 18) | def setUp(self):
    method test_vehicle_expenses_based_on_fiscal_year (line 26) | def test_vehicle_expenses_based_on_fiscal_year(self):

FILE: hrms/hr/report/vehicle_expenses/vehicle_expenses.js
  function set_reqd_filter (line 64) | function set_reqd_filter(fieldname, is_reqd) {

FILE: hrms/hr/report/vehicle_expenses/vehicle_expenses.py
  function execute (line 12) | def execute(filters=None):
  function get_columns (line 22) | def get_columns():
  function get_vehicle_log_data (line 62) | def get_vehicle_log_data(filters):
  function get_conditions (line 93) | def get_conditions(filters):
  function get_period_dates (line 110) | def get_period_dates(filters):
  function get_service_expense (line 120) | def get_service_expense(logname):
  function get_chart_data (line 135) | def get_chart_data(data, filters):

FILE: hrms/hr/utils.py
  class DuplicateDeclarationError (line 48) | class DuplicateDeclarationError(frappe.ValidationError):
  class OverAllocationError (line 52) | class OverAllocationError(frappe.ValidationError):
  function set_employee_name (line 56) | def set_employee_name(doc):
  function update_employee_work_history (line 61) | def update_employee_work_history(employee, details, date=None, cancel=Fa...
  function get_formatted_value (line 101) | def get_formatted_value(value, fieldtype):
  function delete_employee_work_history (line 129) | def delete_employee_work_history(details, employee, date):
  function update_to_date_in_work_history (line 149) | def update_to_date_in_work_history(employee, cancel):
  function get_employee_field_property (line 166) | def get_employee_field_property(employee, fieldname):
  function validate_dates (line 190) | def validate_dates(doc, from_date, to_date, restrict_future_dates=True):
  function validate_overlap (line 204) | def validate_overlap(doc, from_date, to_date, company=None):
  function get_doc_condition (line 236) | def get_doc_condition(doctype):
  function throw_overlap_error (line 248) | def throw_overlap_error(doc, exists_for, overlap_doc, from_date, to_date):
  function validate_duplicate_exemption_for_payroll_period (line 259) | def validate_duplicate_exemption_for_payroll_period(doctype, docname, pa...
  function validate_tax_declaration (line 276) | def validate_tax_declaration(declarations):
  function get_total_exemption_amount (line 284) | def get_total_exemption_amount(declarations):
  function get_leave_period (line 312) | def get_leave_period(from_date, to_date, company):
  function generate_leave_encashment (line 330) | def generate_leave_encashment():
  function allocate_earned_leaves (line 357) | def allocate_earned_leaves():
  function get_upcoming_earned_leave_from_schedule (line 391) | def get_upcoming_earned_leave_from_schedule(allocation_name, today):
  function get_annual_allocation_from_policy (line 399) | def get_annual_allocation_from_policy(allocation, e_leave_type):
  function calculate_upcoming_earned_leave (line 407) | def calculate_upcoming_earned_leave(allocation, e_leave_type, date_of_jo...
  function update_previous_leave_allocation (line 418) | def update_previous_leave_allocation(allocation, annual_allocation, e_le...
  function log_allocation_error (line 454) | def log_allocation_error(allocation_name, error):
  function send_email_for_failed_allocations (line 467) | def send_email_for_failed_allocations(failed_allocations):
  function get_monthly_earned_leave (line 491) | def get_monthly_earned_leave(
  function get_sub_period_start_and_end (line 519) | def get_sub_period_start_and_end(date, frequency):
  function round_earned_leaves (line 528) | def round_earned_leaves(earned_leaves, rounding):
  function get_leave_allocations (line 542) | def get_leave_allocations(date, leave_type):
  function get_earned_leaves (line 576) | def get_earned_leaves():
  function create_additional_leave_ledger_entry (line 590) | def create_additional_leave_ledger_entry(allocation, leaves, date):
  function get_expected_allocation_date_for_period (line 598) | def get_expected_allocation_date_for_period(frequency, allocate_on_day, ...
  function get_salary_assignments (line 618) | def get_salary_assignments(employee, payroll_period):
  function get_sal_slip_total_benefit_given (line 645) | def get_sal_slip_total_benefit_given(employee, payroll_period, component...
  function get_holiday_dates_for_employee (line 678) | def get_holiday_dates_for_employee(employee, start_date, end_date):
  function get_holidays_for_employee (line 686) | def get_holidays_for_employee(employee, start_date, end_date, raise_exce...
  function calculate_annual_eligible_hra_exemption (line 715) | def calculate_annual_eligible_hra_exemption(doc):
  function calculate_hra_exemption_for_period (line 722) | def calculate_hra_exemption_for_period(doc):
  function calculate_tax_with_marginal_relief (line 729) | def calculate_tax_with_marginal_relief(tax_slab, tax_amount, annual_taxa...
  function get_previous_claimed_amount (line 735) | def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=F...
  function share_doc_with_approver (line 764) | def share_doc_with_approver(doc, user):
  function validate_active_employee (line 792) | def validate_active_emplo
Copy disabled (too large) Download .json
Condensed preview — 1451 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (19,359K chars).
[
  {
    "path": ".editorconfig",
    "chars": 277,
    "preview": "# Root editor config file\nroot = true\n\n# Common settings\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_"
  },
  {
    "path": ".git-blame-ignore-revs",
    "chars": 917,
    "preview": "# Since version 2.23 (released in August 2019), git-blame has a feature\n# to ignore or bypass certain commits.\n#\n# This "
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 380,
    "preview": "# This is a comment.\n# Each line is a file pattern followed by one or more owners.\n\n# These owners will be the default o"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "chars": 2660,
    "preview": "---\nname: Bug Report\ndescription: Report a bug encountered while using Frappe HR\nlabels: [\"bug\"]\n\nbody:\n  - type: markdo"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 165,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Community Forum\n    url: https://discuss.frappe.io/\n    about: For "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "chars": 2432,
    "preview": "---\nname: Feature Request\ndescription: Suggest an idea to improve Frappe HR\nlabels: [\"feature-request\"]\n\nbody:\n  - type:"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1213,
    "preview": "<!--\n\nSome key notes before you open a PR:\n\n 1. Select which branch should this PR be merged in?\n 2. PR name follows [co"
  },
  {
    "path": ".github/helper/apps.json",
    "chars": 219,
    "preview": "[\n    {\"url\": \"https://github.com/frappe/erpnext\",\"branch\": \"version-15\"},\n    {\"url\": \"https://github.com/frappe/paymen"
  },
  {
    "path": ".github/helper/documentation.py",
    "chars": 1226,
    "preview": "import sys\nimport requests\nfrom urllib.parse import urlparse\n\n\ndef uri_validator(x):\n\tresult = urlparse(x)\n\treturn all(["
  },
  {
    "path": ".github/helper/install.sh",
    "chars": 2487,
    "preview": "#!/bin/bash\n\nset -e\n\ncd ~ || exit\n\nsudo apt update\nsudo apt remove mysql-server mysql-client\nsudo apt install libcups2-d"
  },
  {
    "path": ".github/helper/site_config.json",
    "chars": 494,
    "preview": "{\n    \"db_host\": \"127.0.0.1\",\n    \"db_port\": 3306,\n    \"db_name\": \"test_frappe\",\n    \"db_password\": \"test_frappe\",\n    \""
  },
  {
    "path": ".github/helper/translation.py",
    "chars": 2093,
    "preview": "import re\nimport sys\n\nerrors_encounter = 0\npattern = re.compile(\n\tr\"_\\(([\\\"']{,3})(?P<message>((?!\\1).)*)\\1(\\s*,\\s*conte"
  },
  {
    "path": ".github/helper/update_pot_file.sh",
    "chars": 1280,
    "preview": "#!/bin/bash\nset -e\ncd ~ || exit\n\necho \"Setting Up Bench...\"\n\npip install frappe-bench\nbench -v init frappe-bench --skip-"
  },
  {
    "path": ".github/labeler.yml",
    "chars": 120,
    "preview": "# Any python files modifed but no test files modified\nneeds-tests:\n- any: ['hrms/**/*.py']\n  all: ['!hrms/**/test*.py']\n"
  },
  {
    "path": ".github/release.yml",
    "chars": 61,
    "preview": "changelog:\n  exclude:\n    labels:\n      - skip-release-notes\n"
  },
  {
    "path": ".github/workflows/build_image.yml",
    "chars": 1775,
    "preview": "name: Build Container Image\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n  push:\n    branches:\n      - ver"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 3973,
    "preview": "name: CI\n\non:\n  pull_request:\n    paths-ignore:\n      - \"**.css\"\n      - \"**.js\"\n      - \"**.md\"\n      - \"**.html\"\n     "
  },
  {
    "path": ".github/workflows/docs_checker.yml",
    "chars": 582,
    "preview": "name: 'Documentation Required'\non:\n  pull_request:\n    types: [ opened, synchronize, reopened, edited ]\n\njobs:\n  build:\n"
  },
  {
    "path": ".github/workflows/generate-pot-file.yml",
    "chars": 956,
    "preview": "name: Regenerate POT file (translatable strings)\non:\n  schedule:\n    # 9:30 UTC => 3 PM IST Sunday\n    - cron: \"30 9 * *"
  },
  {
    "path": ".github/workflows/initiate_release.yml",
    "chars": 860,
    "preview": "# This workflow is agnostic to branches. Only maintain on develop branch.\n# To add/remove versions just modify the matri"
  },
  {
    "path": ".github/workflows/labeller.yml",
    "chars": 234,
    "preview": "name: \"Pull Request Labeler\"\non:\n  pull_request_target:\n    types: [opened, reopened]\n\njobs:\n  triage:\n    runs-on: ubun"
  },
  {
    "path": ".github/workflows/linters.yml",
    "chars": 1289,
    "preview": "name: Linters\n\non:\n  pull_request: { }\n\njobs:\n  commit-lint:\n    name: 'Semantic Commits'\n    runs-on: ubuntu-latest\n   "
  },
  {
    "path": ".github/workflows/on_release.yml",
    "chars": 924,
    "preview": "name: Generate Semantic Release\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - version-14\njobs:\n  release:\n    n"
  },
  {
    "path": ".github/workflows/release_notes.yml",
    "chars": 1294,
    "preview": "# This action:\n#\n# 1. Generates release notes using github API.\n# 2. Strips unnecessary info like chore/style etc from n"
  },
  {
    "path": ".github/workflows/run-individual-tests.yml",
    "chars": 4190,
    "preview": "name: Individual\n\non:\n  workflow_dispatch:\n\nconcurrency:\n  group: server-individual-tests-lightmode-develop\n  cancel-in-"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 1744,
    "preview": "# https://github.com/actions/stale\n\nname: \"Close Stale PRs\"\non:\n  schedule:\n    - cron: 0 0 * * *\n  workflow_dispatch:\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 275,
    "preview": ".DS_Store\n*.pyc\n*.egg-info\n*.swp\ntags\nhrms/public/dist\nhrms/public/node_modules\nhrms/docs/current\nnode_modules/\ndist/\n__"
  },
  {
    "path": ".gitmodules",
    "chars": 85,
    "preview": "[submodule \"frappe-ui\"]\n\tpath = frappe-ui\n\turl = https://github.com/frappe/frappe-ui\n"
  },
  {
    "path": ".mergify.yml",
    "chars": 2279,
    "preview": "pull_request_rules:\n  - name: Auto-close PRs on stable branch\n    conditions:\n      - and:\n        - and:\n          - au"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1332,
    "preview": "exclude: 'node_modules|.git'\ndefault_stages: [commit]\nfail_fast: false\n\n\nrepos:\n  - repo: https://github.com/pre-commit/"
  },
  {
    "path": ".releaserc",
    "chars": 581,
    "preview": "{\n\t\"branches\": [\"version-14\"],\n\t\"plugins\": [\n\t\t\"@semantic-release/commit-analyzer\", {\n\t\t\t\"preset\": \"angular\",\n\t\t\t\"releas"
  },
  {
    "path": ".semgrepignore",
    "chars": 27,
    "preview": "hrms/patches/post_install/\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3212,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "MANIFEST.in",
    "chars": 422,
    "preview": "include MANIFEST.in\ninclude *.json\ninclude *.md\ninclude *.py\ninclude *.txt\nrecursive-include hrms *.css\nrecursive-includ"
  },
  {
    "path": "README.md",
    "chars": 6499,
    "preview": "<div align=\"center\">\n\t<a href=\"https://frappe.io/hr\">\n\t\t<img src=\".github/frappe-hr-logo.png\" height=\"80px\" width=\"80px\""
  },
  {
    "path": "SECURITY.md",
    "chars": 475,
    "preview": "# Security Policy\n\nThe Frappe HR team and community take security issues seriously. To report a security issue, please g"
  },
  {
    "path": "codecov.yml",
    "chars": 368,
    "preview": "codecov:\n  require_ci_to_pass: yes\n\ncoverage:\n  status:\n    project:\n      default:\n        target: auto\n        thresho"
  },
  {
    "path": "commitlint.config.js",
    "chars": 407,
    "preview": "module.exports = {\n\tparserPreset: \"conventional-changelog-conventionalcommits\",\n\trules: {\n\t\t\"subject-empty\": [2, \"never\""
  },
  {
    "path": "crowdin.yml",
    "chars": 343,
    "preview": "languages_mapping:\n  two_letters_code:\n    pt-BR: pt_BR\nfiles:\n  - source: /hrms/locale/main.pot\n    translation: /hrms/"
  },
  {
    "path": "docker/docker-compose.yml",
    "chars": 686,
    "preview": "version: \"3.8\" # Updated version\nservices:\n  mariadb:\n    image: mariadb:10.8\n    command:\n      - --character-set-serve"
  },
  {
    "path": "docker/init.sh",
    "chars": 1033,
    "preview": "#!bin/bash\n\nif [ -d \"/home/frappe/frappe-bench/apps/frappe\" ]; then\n    echo \"Bench already exists, skipping init\"\n    c"
  },
  {
    "path": "frontend/.eslintrc.js",
    "chars": 501,
    "preview": "module.exports = {\n\troot: true,\n\tenv: {\n\t\tes2021: true,\n\t\tnode: true,\n\t},\n\textends: [\n\t\t\"eslint:recommended\",\n\t\t\"plugin:"
  },
  {
    "path": "frontend/.gitignore",
    "chars": 53,
    "preview": "node_modules\n.DS_Store\ndev-dist\ndist\ndist-ssr\n*.local"
  },
  {
    "path": "frontend/.prettierrc.json",
    "chars": 53,
    "preview": "{\n\t\"semi\": false,\n\t\"tabWidth\": 2,\n\t\"useTabs\": true\n}\n"
  },
  {
    "path": "frontend/index.html",
    "chars": 8070,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<meta\n\t\t\tname=\"viewport\"\n\t\t\tcontent=\"width=device-"
  },
  {
    "path": "frontend/ionic.config.json",
    "chars": 112,
    "preview": "{\n\t\"name\": \"FrappeHR\",\n\t\"integrations\": {},\n\t\"type\": \"vue\",\n\t\"server\": {\n\t\t\"url\": \"http://localhost:8080/\"\n\t}\n}\n"
  },
  {
    "path": "frontend/jsconfig.json",
    "chars": 47,
    "preview": "{\n\t\"compilerOptions\": {\n\t\t\"allowJs\": true\n\t}\n}\n"
  },
  {
    "path": "frontend/package.json",
    "chars": 946,
    "preview": "{\n\t\"name\": \"frappe-hr-ui\",\n\t\"private\": true,\n\t\"version\": \"0.0.0\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"dev\": \"vite\",\n\t\t\"s"
  },
  {
    "path": "frontend/postcss.config.js",
    "chars": 74,
    "preview": "export default {\n\tplugins: {\n\t\ttailwindcss: {},\n\t\tautoprefixer: {},\n\t},\n}\n"
  },
  {
    "path": "frontend/public/frappe-push-notification.js",
    "chars": 7425,
    "preview": "import { initializeApp } from \"firebase/app\"\nimport {\n\tgetMessaging,\n\tgetToken,\n\tisSupported,\n\tdeleteToken,\n\tonMessage a"
  },
  {
    "path": "frontend/public/sw.js",
    "chars": 1731,
    "preview": "import { cleanupOutdatedCaches, precacheAndRoute } from \"workbox-precaching\"\nimport { clientsClaim } from \"workbox-core\""
  },
  {
    "path": "frontend/src/App.vue",
    "chars": 502,
    "preview": "<template>\n\t<ion-app>\n\t\t<ion-router-outlet id=\"main-content\" />\n\t\t<Toasts />\n\n\t\t<InstallPrompt />\n\t</ion-app>\n</template"
  },
  {
    "path": "frontend/src/components/AttendanceCalendar.vue",
    "chars": 3563,
    "preview": "<template>\n\t<div class=\"flex flex-col w-full gap-5\" v-if=\"calendarEvents.data\">\n\t\t<div class=\"text-lg text-gray-800 font"
  },
  {
    "path": "frontend/src/components/AttendanceRequestItem.vue",
    "chars": 1554,
    "preview": "<template>\n\t<ListItem\n\t\t:isTeamRequest=\"props.isTeamRequest\"\n\t\t:employee=\"props.doc.employee\"\n\t\t:employeeName=\"props.doc"
  },
  {
    "path": "frontend/src/components/BaseLayout.vue",
    "chars": 1796,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-header class=\"ion-no-border\">\n\t\t\t<div class=\"w-full sm:w-96\">\n\t\t\t\t<div class=\"flex flex-co"
  },
  {
    "path": "frontend/src/components/BottomTabs.vue",
    "chars": 1554,
    "preview": "<template>\n\t<ion-tab-bar\n\t\tslot=\"bottom\"\n\t\tclass=\"bg-white shadow-md sm:w-96 py-2 pb-2 standalone:pb-safe-bottom\"\n\t>\n\t\t<"
  },
  {
    "path": "frontend/src/components/CheckInPanel.vue",
    "chars": 5877,
    "preview": "<template>\n\t<div class=\"flex flex-col bg-white rounded w-full py-6 px-4 border-none\">\n\t\t<h2 class=\"text-lg font-bold tex"
  },
  {
    "path": "frontend/src/components/CustomIonModal.vue",
    "chars": 1125,
    "preview": "<template>\n\t<ion-modal\n\t\tref=\"modal\"\n\t\t:trigger=\"trigger\"\n\t\t:initial-breakpoint=\"1\"\n\t\t:breakpoints=\"[0, 1]\"\n\t\t:backdrop-"
  },
  {
    "path": "frontend/src/components/EmployeeAdvanceBalance.vue",
    "chars": 1007,
    "preview": "<template>\n\t<div\n\t\tclass=\"flex flex-col bg-white rounded mt-5 overflow-auto\"\n\t\tv-if=\"props.items?.length\"\n\t>\n\t\t<router-l"
  },
  {
    "path": "frontend/src/components/EmployeeAdvanceItem.vue",
    "chars": 2095,
    "preview": "<template>\n\t<ListItem\n\t\t:isTeamRequest=\"props.isTeamRequest\"\n\t\t:employee=\"props.doc.employee\"\n\t\t:employeeName=\"props.doc"
  },
  {
    "path": "frontend/src/components/EmployeeAvatar.vue",
    "chars": 996,
    "preview": "<template>\n\t<div v-if=\"showLabel\" class=\"flex flex-row items-center gap-2\">\n\t\t<Avatar\n\t\t\tv-if=\"employee\"\n\t\t\t:label=\"empl"
  },
  {
    "path": "frontend/src/components/EmployeeCheckinItem.vue",
    "chars": 750,
    "preview": "<template>\n\t<ListItem>\n\t\t<template #left>\n\t\t\t<FeatherIcon name=\"clock\" class=\"h-5 w-5 text-gray-500\" />\n\t\t\t<div class=\"f"
  },
  {
    "path": "frontend/src/components/EmptyState.vue",
    "chars": 365,
    "preview": "<template>\n\t<div\n\t\tclass=\"flex flex-col items-center rounded p-5 text-sm text-gray-600\"\n\t\t:class=\"[\n\t\t\tprops.isTableFiel"
  },
  {
    "path": "frontend/src/components/ExpenseAdvancesTable.vue",
    "chars": 2387,
    "preview": "<template>\n\t<div class=\"flex flex-row justify-between items-center\">\n\t\t<h2 class=\"text-base font-semibold text-gray-800\""
  },
  {
    "path": "frontend/src/components/ExpenseClaimItem.vue",
    "chars": 2859,
    "preview": "<template>\n\t<ListItem\n\t\t:isTeamRequest=\"props.isTeamRequest\"\n\t\t:employee=\"props.doc.employee\"\n\t\t:employeeName=\"props.doc"
  },
  {
    "path": "frontend/src/components/ExpenseClaimSummary.vue",
    "chars": 2666,
    "preview": "<template>\n\t<div class=\"flex flex-col w-full gap-5\" v-if=\"summary.data\">\n\t\t<div class=\"text-lg text-gray-800 font-bold\">"
  },
  {
    "path": "frontend/src/components/ExpenseItems.vue",
    "chars": 1613,
    "preview": "<template>\n\t<!-- Table -->\n\t<div\n\t\tv-if=\"doc?.expenses\"\n\t\tclass=\"flex flex-col bg-white mt-5 rounded border overflow-aut"
  },
  {
    "path": "frontend/src/components/ExpenseTaxesTable.vue",
    "chars": 7093,
    "preview": "<template>\n\t<template v-if=\"expenseClaim.expenses\">\n\t\t<div class=\"flex flex-row justify-between items-center pt-4\">\n\t\t\t<"
  },
  {
    "path": "frontend/src/components/ExpensesTable.vue",
    "chars": 6682,
    "preview": "<template>\n\t<!-- Header -->\n\t<div class=\"flex flex-row justify-between items-center mt-2\">\n\t\t<h2 class=\"text-base font-s"
  },
  {
    "path": "frontend/src/components/FilePreviewModal.vue",
    "chars": 1244,
    "preview": "<template>\n\t<ion-header>\n\t\t<ion-toolbar>\n\t\t\t<ion-title>{{ filename }} - {{ __(\"File Preview\") }}</ion-title>\n\t\t\t<ion-but"
  },
  {
    "path": "frontend/src/components/FileUploaderView.vue",
    "chars": 3120,
    "preview": "<template>\n\t<div class=\"flex flex-col gap-3 py-4\">\n\t\t<label class=\"file-select\">\n\t\t\t<h2 class=\"text-base font-semibold t"
  },
  {
    "path": "frontend/src/components/FormField.vue",
    "chars": 5896,
    "preview": "<template>\n\t<div v-if=\"showField\" class=\"flex flex-col gap-1.5\">\n\t\t<!-- Label -->\n\t\t<span\n\t\t\tv-if=\"!['Check', 'Section B"
  },
  {
    "path": "frontend/src/components/FormView.vue",
    "chars": 19408,
    "preview": "<template>\n\t<div class=\"flex flex-col h-full w-full\" v-if=\"isFormReady\">\n\t\t<div class=\"w-full h-full bg-white sm:w-96 fl"
  },
  {
    "path": "frontend/src/components/FormattedField.vue",
    "chars": 1972,
    "preview": "<template>\n\t<div v-if=\"!props.value\" class=\"text-gray-600 text-base\">-</div>\n\n\t<Badge\n\t\tv-else-if=\"props.fieldtype === '"
  },
  {
    "path": "frontend/src/components/Holidays.vue",
    "chars": 3045,
    "preview": "<template>\n\t<div class=\"flex flex-col gap-5 w-full\">\n\t\t<div class=\"flex flex-row justify-between items-center\">\n\t\t\t<div "
  },
  {
    "path": "frontend/src/components/InstallPrompt.vue",
    "chars": 3029,
    "preview": "<template>\n\t<!-- Install PWA dialog -->\n\t<Dialog v-model=\"showDialog\">\n\t\t<template #body-title>\n\t\t\t<h2 class=\"text-lg fo"
  },
  {
    "path": "frontend/src/components/LeaveBalance.vue",
    "chars": 1813,
    "preview": "<template>\n\t<div class=\"flex flex-col w-full\">\n\t\t<div class=\"flex flex-row justify-between items-center px-4\">\n\t\t\t<div c"
  },
  {
    "path": "frontend/src/components/LeaveRequestItem.vue",
    "chars": 1563,
    "preview": "<template>\n\t<ListItem\n\t\t:isTeamRequest=\"props.isTeamRequest\"\n\t\t:employee=\"props.doc.employee\"\n\t\t:employeeName=\"props.doc"
  },
  {
    "path": "frontend/src/components/Link.vue",
    "chars": 1971,
    "preview": "<template>\n\t<Autocomplete\n\t\tref=\"autocompleteRef\"\n\t\tsize=\"sm\"\n\t\tv-model=\"value\"\n\t\t:placeholder=\"__('Select {0}', [__(doc"
  },
  {
    "path": "frontend/src/components/ListFiltersActionSheet.vue",
    "chars": 3396,
    "preview": "<template>\n\t<!-- Filter Action Sheet -->\n\t<div\n\t\tclass=\"bg-white w-full flex flex-col items-center justify-center pb-5 m"
  },
  {
    "path": "frontend/src/components/ListItem.vue",
    "chars": 853,
    "preview": "<template>\n\t<div class=\"flex flex-col w-full justify-center gap-2.5\">\n\t\t<div class=\"flex flex-row items-center justify-b"
  },
  {
    "path": "frontend/src/components/ListView.vue",
    "chars": 10612,
    "preview": "<template>\n\t<ion-header class=\"ion-no-border\">\n\t\t<div class=\"w-full sm:w-96\">\n\t\t\t<div\n\t\t\t\tclass=\"flex flex-row bg-white "
  },
  {
    "path": "frontend/src/components/ProfileInfoModal.vue",
    "chars": 1027,
    "preview": "<template>\n\t<div\n\t\tclass=\"bg-white w-full flex flex-col items-center justify-center pb-5 max-h-[calc(100vh-5rem)]\"\n\t>\n\t\t"
  },
  {
    "path": "frontend/src/components/QuickLinks.vue",
    "chars": 990,
    "preview": "<template>\n\t<div class=\"flex flex-col gap-5 my-4 w-full\">\n\t\t<div class=\"text-lg font-medium text-gray-900\">{{ title || _"
  },
  {
    "path": "frontend/src/components/RequestActionSheet.vue",
    "chars": 8585,
    "preview": "<template>\n\t<div\n\t\tv-if=\"document?.doc\"\n\t\tclass=\"bg-white w-full flex flex-col items-center justify-center pb-5 max-h-[c"
  },
  {
    "path": "frontend/src/components/RequestList.vue",
    "chars": 2333,
    "preview": "<template>\n\t<div class=\"flex flex-col bg-white rounded mt-5 overflow-auto\" v-if=\"props.items?.length\">\n\t\t<div\n\t\t\tclass=\""
  },
  {
    "path": "frontend/src/components/RequestPanel.vue",
    "chars": 2536,
    "preview": "<template>\n\t<div class=\"w-full\">\n\t\t<TabButtons\n\t\t\t:buttons=\"TAB_BUTTONS\"\n\t\t\tv-model=\"activeTab\"\n\t\t/>\n\t\t<RequestList v-if"
  },
  {
    "path": "frontend/src/components/SalaryDetailTable.vue",
    "chars": 1555,
    "preview": "<template>\n\t<!-- Header -->\n\t<div class=\"flex flex-row justify-between items-center\">\n\t\t<h2 class=\"text-base font-semibo"
  },
  {
    "path": "frontend/src/components/SalarySlipItem.vue",
    "chars": 1517,
    "preview": "<template>\n\t<ListItem>\n\t\t<template #left>\n\t\t\t<SalaryIcon class=\"h-5 w-5 text-gray-500\" />\n\t\t\t<div class=\"flex flex-col i"
  },
  {
    "path": "frontend/src/components/SemicircleChart.vue",
    "chars": 1113,
    "preview": "<template>\n\t<svg\n\t\tviewBox=\"0 0 48 24\"\n\t\tpreserveAspectRatio=\"xMidYMin slice\"\n\t\tclass=\"h-[84px] w-[84px] -mt-10\"\n\t>\n\t\t<c"
  },
  {
    "path": "frontend/src/components/ShiftAssignmentItem.vue",
    "chars": 1540,
    "preview": "<template>\n\t<ListItem>\n\t\t<template #left>\n\t\t\t<ShiftIcon class=\"h-5 w-5 text-gray-500\" />\n\t\t\t<div class=\"flex flex-col it"
  },
  {
    "path": "frontend/src/components/ShiftRequestItem.vue",
    "chars": 1635,
    "preview": "<template>\n\t<ListItem\n\t\t:isTeamRequest=\"props.isTeamRequest\"\n\t\t:employee=\"props.doc.employee\"\n\t\t:employeeName=\"props.doc"
  },
  {
    "path": "frontend/src/components/TabButtons.vue",
    "chars": 693,
    "preview": "<template>\n\t<div class=\"flex p-1 bg-gray-200 rounded\">\n\t\t<button\n\t\t\tv-for=\"button in buttons\"\n\t\t\t:key=\"button.key ?? but"
  },
  {
    "path": "frontend/src/components/WorkflowActionSheet.vue",
    "chars": 3153,
    "preview": "<template>\n\t<div\n\t\tv-if=\"actions.length > 0\"\n\t\t:class=\"[\n\t\t\tprops.view === 'form'\n\t\t\t\t? 'px-4 pt-4 pb-4 standalone:pb-sa"
  },
  {
    "path": "frontend/src/components/icons/AttendanceIcon.vue",
    "chars": 381,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\twidth=\"24\"\n\t\theight=\"24\"\n\t\tviewBox=\"0 0 24 24\"\n\t\tfill=\"none\"\n\t\ts"
  },
  {
    "path": "frontend/src/components/icons/EmployeeAdvanceIcon.vue",
    "chars": 1816,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\tfill=\"none\"\n\t\tviewBox=\"-0.855 -0.855 24 24\"\n\t\theight=\"24\"\n\t\twidt"
  },
  {
    "path": "frontend/src/components/icons/ExpenseIcon.vue",
    "chars": 1836,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\tfill=\"none\"\n\t\tviewBox=\"-1 -1 28 28\"\n\t\theight=\"24\"\n\t\twidth=\"24\"\n\t"
  },
  {
    "path": "frontend/src/components/icons/FrappeHRLogo.vue",
    "chars": 1258,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\twidth=\"117\"\n\t\theight=\"117\"\n\t\tviewBox=\"0 0 117 117\"\n\t\tfill=\"none\""
  },
  {
    "path": "frontend/src/components/icons/FrappeHRLogoType.vue",
    "chars": 7816,
    "preview": "<template>\n\t<svg\n\t\twidth=\"116\"\n\t\theight=\"30\"\n\t\tviewBox=\"0 0 116 30\"\n\t\tfill=\"none\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t"
  },
  {
    "path": "frontend/src/components/icons/HomeIcon.vue",
    "chars": 373,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\twidth=\"24\"\n\t\theight=\"24\"\n\t\tviewBox=\"0 0 24 24\"\n\t\tfill=\"none\"\n\t\ts"
  },
  {
    "path": "frontend/src/components/icons/LeaveIcon.vue",
    "chars": 451,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\twidth=\"24\"\n\t\theight=\"24\"\n\t\tviewBox=\"0 0 24 24\"\n\t\tfill=\"none\"\n\t\ts"
  },
  {
    "path": "frontend/src/components/icons/SalaryIcon.vue",
    "chars": 1859,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\tfill=\"none\"\n\t\tviewBox=\"-1 -1 28 28\"\n\t\theight=\"24\"\n\t\twidth=\"24\"\n\t"
  },
  {
    "path": "frontend/src/components/icons/ShiftIcon.vue",
    "chars": 491,
    "preview": "<template>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\twidth=\"24\"\n\t\theight=\"24\"\n\t\tviewBox=\"0 0 24 24\"\n\t\tfill=\"none\"\n\t\ts"
  },
  {
    "path": "frontend/src/composables/index.js",
    "chars": 2745,
    "preview": "import { createResource, toast } from \"frappe-ui\"\n\nfunction getFileReader() {\n\tconst fileReader = new FileReader()\n\tcons"
  },
  {
    "path": "frontend/src/composables/realtime.js",
    "chars": 402,
    "preview": "import { reactive } from \"vue\"\n\nconst subscribed = reactive({})\n\nexport function useListUpdate(socket, doctype, callback"
  },
  {
    "path": "frontend/src/composables/workflow.js",
    "chars": 2640,
    "preview": "import { createResource, toast } from \"frappe-ui\"\nimport { computed } from \"vue\"\nimport { userResource } from \"@/data/us"
  },
  {
    "path": "frontend/src/data/advances.js",
    "chars": 384,
    "preview": "import { createResource } from \"frappe-ui\"\n\nconst transformAdvanceData = (data) => {\n\treturn data.map((claim) => {\n\t\tcla"
  },
  {
    "path": "frontend/src/data/attendance.js",
    "chars": 2870,
    "preview": "import { createResource } from \"frappe-ui\"\nimport { employeeResource } from \"./employee\"\n\nimport dayjs from \"@/utils/day"
  },
  {
    "path": "frontend/src/data/claims.js",
    "chars": 1264,
    "preview": "import { createResource } from \"frappe-ui\"\nimport { employeeResource } from \"./employee\"\nimport { reactive } from \"vue\"\n"
  },
  {
    "path": "frontend/src/data/config/requestSummaryFields.js",
    "chars": 4173,
    "preview": "// This config holds the fields that should be shown in the request summary action sheet\n// TODO: This should be config-"
  },
  {
    "path": "frontend/src/data/currencies.js",
    "chars": 526,
    "preview": "import { createResource } from \"frappe-ui\"\n\nconst companyCurrency = createResource({\n\turl: \"hrms.api.get_company_currenc"
  },
  {
    "path": "frontend/src/data/employee.js",
    "chars": 305,
    "preview": "import router from \"@/router\"\nimport { createResource } from \"frappe-ui\"\n\nexport const employeeResource = createResource"
  },
  {
    "path": "frontend/src/data/employees.js",
    "chars": 861,
    "preview": "import { createResource } from \"frappe-ui\"\nimport { reactive } from \"vue\"\nimport { employeeResource } from \"./employee\"\n"
  },
  {
    "path": "frontend/src/data/leaves.js",
    "chars": 1602,
    "preview": "import { createResource } from \"frappe-ui\"\nimport { employeeResource } from \"./employee\"\n\nimport dayjs from \"@/utils/day"
  },
  {
    "path": "frontend/src/data/notifications.js",
    "chars": 845,
    "preview": "import { createResource, createListResource } from \"frappe-ui\"\nimport { userResource } from \"./user\"\n\nexport const unrea"
  },
  {
    "path": "frontend/src/data/session.js",
    "chars": 1241,
    "preview": "import { computed, reactive } from \"vue\"\nimport { createResource, call } from \"frappe-ui\"\nimport { userResource } from \""
  },
  {
    "path": "frontend/src/data/user.js",
    "chars": 302,
    "preview": "import router from \"@/router\"\nimport { createResource } from \"frappe-ui\"\n\nexport const userResource = createResource({\n\t"
  },
  {
    "path": "frontend/src/main.css",
    "chars": 565,
    "preview": "@import \"frappe-ui/src/style.css\";\n\nion-modal {\n\t--height: auto;\n}\n\ninput:disabled {\n\t--webkit-text-fill-color: var(--tw"
  },
  {
    "path": "frontend/src/main.js",
    "chars": 3559,
    "preview": "import { createApp } from \"vue\"\nimport App from \"./App.vue\"\nimport router from \"./router\"\nimport { initSocket } from \"./"
  },
  {
    "path": "frontend/src/plugins/translationsPlugin.js",
    "chars": 1923,
    "preview": "function makeTranslationFunction() {\n\tlet messages = {};\n\treturn {\n\t\ttranslate,\n\t\tload: () => Promise.allSettled([\n\t\t\tse"
  },
  {
    "path": "frontend/src/router/advances.js",
    "chars": 475,
    "preview": "const routes = [\n\t{\n\t\tname: \"EmployeeAdvanceListView\",\n\t\tpath: \"/employee-advances\",\n\t\tcomponent: () => import(\"@/views/"
  },
  {
    "path": "frontend/src/router/attendance.js",
    "chars": 1558,
    "preview": "const routes = [\n\t{\n\t\tname: \"AttendanceRequestListView\",\n\t\tpath: \"/attendance-requests\",\n\t\tcomponent: () => import(\"@/vi"
  },
  {
    "path": "frontend/src/router/claims.js",
    "chars": 448,
    "preview": "const routes = [\n\t{\n\t\tname: \"ExpenseClaimListView\",\n\t\tpath: \"/expense-claims\",\n\t\tcomponent: () => import(\"@/views/expens"
  },
  {
    "path": "frontend/src/router/index.js",
    "chars": 1901,
    "preview": "import { createRouter, createWebHistory } from \"@ionic/vue-router\"\n\nimport TabbedView from \"@/views/TabbedView.vue\"\nimpo"
  },
  {
    "path": "frontend/src/router/leaves.js",
    "chars": 448,
    "preview": "const routes = [\n\t{\n\t\tname: \"LeaveApplicationListView\",\n\t\tpath: \"/leave-applications\",\n\t\tcomponent: () => import(\"@/view"
  },
  {
    "path": "frontend/src/router/salary_slips.js",
    "chars": 186,
    "preview": "const routes = [\n\t{\n\t\tpath: \"/salary-slips/:id\",\n\t\tname: \"SalarySlipDetailView\",\n\t\tprops: true,\n\t\tcomponent: () => impor"
  },
  {
    "path": "frontend/src/socket.js",
    "chars": 843,
    "preview": "import { io } from \"socket.io-client\"\nimport { socketio_port } from \"../../../../sites/common_site_config.json\"\n\nimport "
  },
  {
    "path": "frontend/src/theme/variables.css",
    "chars": 2529,
    "preview": "/* Ionic Variables and Theming. For more info, please see:\nhttp://ionicframework.com/docs/theming/ */\n\n/** Ionic CSS Var"
  },
  {
    "path": "frontend/src/utils/commonUtils.js",
    "chars": 1353,
    "preview": "import { toast } from \"frappe-ui\"\n\nexport function useDownloadPDF() {\n\tasync function downloadPDF({ doctype, docname, fi"
  },
  {
    "path": "frontend/src/utils/dayjs.js",
    "chars": 511,
    "preview": "import dayjs from \"dayjs\"\nimport updateLocale from \"dayjs/plugin/updateLocale\"\nimport localizedFormat from \"dayjs/plugin"
  },
  {
    "path": "frontend/src/utils/dialogs.js",
    "chars": 222,
    "preview": "export const showErrorAlert = async (message) => {\n\tconst alert = await alertController.create({\n\t\theader: \"Error\",\n\t\tme"
  },
  {
    "path": "frontend/src/utils/formatters.js",
    "chars": 1296,
    "preview": "import { createDocumentResource } from \"frappe-ui\"\n\nimport dayjs from \"@/utils/dayjs\"\n\nconst settings = createDocumentRe"
  },
  {
    "path": "frontend/src/utils/ionicConfig.js",
    "chars": 967,
    "preview": "import { isPlatform } from \"@ionic/vue\"\nimport { createAnimation, iosTransitionAnimation } from \"@ionic/core\"\n/**\n * on "
  },
  {
    "path": "frontend/src/utils/pushNotifications.js",
    "chars": 808,
    "preview": "export const isChrome = () =>\n\tnavigator.userAgent.toLowerCase().includes(\"chrome\")\n\nexport const showNotification = (pa"
  },
  {
    "path": "frontend/src/views/AppSettings.vue",
    "chars": 3901,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content class=\"ion-padding\">\n\t\t\t<div class=\"flex flex-col h-screen w-screen\">\n\t\t\t\t<div cla"
  },
  {
    "path": "frontend/src/views/Home.vue",
    "chars": 1619,
    "preview": "<template>\n\t<BaseLayout>\n\t\t<template #body>\n\t\t\t<div class=\"flex flex-col items-center my-7 p-4 gap-7\">\n\t\t\t\t<CheckInPanel"
  },
  {
    "path": "frontend/src/views/InvalidEmployee.vue",
    "chars": 947,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content class=\"ion-padding\">\n\t\t\t<div class=\"flex h-screen w-screen flex-col justify-center"
  },
  {
    "path": "frontend/src/views/Login.vue",
    "chars": 5272,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content class=\"ion-padding\">\n\t\t\t<div class=\"flex h-screen w-screen flex-col justify-center"
  },
  {
    "path": "frontend/src/views/Notifications.vue",
    "chars": 4493,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content class=\"ion-padding\">\n\t\t\t<div class=\"flex flex-col h-screen w-screen\">\n\t\t\t\t<div cla"
  },
  {
    "path": "frontend/src/views/Profile.vue",
    "chars": 6857,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content class=\"ion-padding\">\n\t\t\t<div class=\"flex flex-col h-screen w-screen\">\n\t\t\t\t<div cla"
  },
  {
    "path": "frontend/src/views/TabbedView.vue",
    "chars": 278,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-tabs>\n\t\t\t<ion-router-outlet></ion-router-outlet>\n\t\t\t<BottomTabs />\n\t\t</ion-tabs>\n\t</ion-pa"
  },
  {
    "path": "frontend/src/views/attendance/AttendanceRequestForm.vue",
    "chars": 2336,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content :fullscreen=\"true\">\n\t\t\t<FormView\n\t\t\t\tv-if=\"formFields.data\"\n\t\t\t\tdoctype=\"Attendanc"
  },
  {
    "path": "frontend/src/views/attendance/AttendanceRequestList.vue",
    "chars": 746,
    "preview": "<template>\n\t<ion-page>\n\t\t<ListView\n\t\t\tdoctype=\"Attendance Request\"\n\t\t\t:pageTitle=\"__('Attendance Request History')\"\n\t\t\t:"
  },
  {
    "path": "frontend/src/views/attendance/Dashboard.vue",
    "chars": 3078,
    "preview": "<template>\n\t<BaseLayout pageTitle=\"Attendance\">\n\t\t<template #body>\n\t\t\t<div class=\"flex flex-col mt-7 mb-7 p-4 gap-7\">\n\t\t"
  },
  {
    "path": "frontend/src/views/attendance/EmployeeCheckinList.vue",
    "chars": 699,
    "preview": "<template>\n\t<ion-page>\n\t\t<ListView\n\t\t\tdoctype=\"Employee Checkin\"\n\t\t\t:pageTitle=\"__('Employee Checkin History')\"\n\t\t\t:fiel"
  },
  {
    "path": "frontend/src/views/attendance/ShiftAssignmentForm.vue",
    "chars": 1941,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content :fullscreen=\"true\">\n\t\t\t<FormView\n\t\t\t\tv-if=\"formFields.data\"\n\t\t\t\tdoctype=\"Shift Ass"
  },
  {
    "path": "frontend/src/views/attendance/ShiftAssignmentList.vue",
    "chars": 758,
    "preview": "<template>\n\t<ion-page>\n\t\t<ListView\n\t\t\tdoctype=\"Shift Assignment\"\n\t\t\t:pageTitle=\"__('Shift Assignment History')\"\n\t\t\t:fiel"
  },
  {
    "path": "frontend/src/views/attendance/ShiftRequestForm.vue",
    "chars": 2422,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content :fullscreen=\"true\">\n\t\t\t<FormView\n\t\t\t\tv-if=\"formFields.data\"\n\t\t\t\tdoctype=\"Shift Req"
  },
  {
    "path": "frontend/src/views/attendance/ShiftRequestList.vue",
    "chars": 1259,
    "preview": "<template>\n\t<ion-page>\n\t\t<ListView\n\t\t\tdoctype=\"Shift Request\"\n\t\t\tpageTitle=\"Shift Request History\"\n\t\t\t:tabButtons=\"TAB_B"
  },
  {
    "path": "frontend/src/views/employee_advance/Form.vue",
    "chars": 3599,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content :fullscreen=\"true\">\n\t\t\t<FormView\n\t\t\t\tv-if=\"formFields.data\"\n\t\t\t\tdoctype=\"Employee "
  },
  {
    "path": "frontend/src/views/employee_advance/List.vue",
    "chars": 1560,
    "preview": "<template>\n\t<ion-page>\n\t\t<ListView\n\t\t\tdoctype=\"Employee Advance\"\n\t\t\t:pageTitle=\"__('Employee Advances')\"\n\t\t\t:tabButtons="
  },
  {
    "path": "frontend/src/views/expense_claim/Dashboard.vue",
    "chars": 1758,
    "preview": "<template>\n\t<BaseLayout :pageTitle=\"__('Expense Claims')\">\n\t\t<template #body>\n\t\t\t<div class=\"flex flex-col mt-7 mb-7 p-4"
  },
  {
    "path": "frontend/src/views/expense_claim/Form.vue",
    "chars": 10700,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content :fullscreen=\"true\">\n\t\t\t<FormView\n\t\t\t\tv-if=\"formFields.data\"\n\t\t\t\tdoctype=\"Expense C"
  },
  {
    "path": "frontend/src/views/expense_claim/List.vue",
    "chars": 1508,
    "preview": "<template>\n\t<ion-page>\n\t\t<ListView\n\t\t\tdoctype=\"Expense Claim\"\n\t\t\t:pageTitle=\"('Claim History')\"\n\t\t\t:tabButtons=\"TAB_BUTT"
  },
  {
    "path": "frontend/src/views/leave/Dashboard.vue",
    "chars": 1245,
    "preview": "<template>\n\t<BaseLayout :pageTitle=\"__('Leaves & Holidays')\">\n\t\t<template #body>\n\t\t\t<div class=\"flex flex-col items-cent"
  },
  {
    "path": "frontend/src/views/leave/Form.vue",
    "chars": 7235,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content :fullscreen=\"true\">\n\t\t\t<FormView\n\t\t\t\tv-if=\"formFields.data\"\n\t\t\t\tdoctype=\"Leave App"
  },
  {
    "path": "frontend/src/views/leave/List.vue",
    "chars": 1319,
    "preview": "<template>\n\t<ion-page>\n\t\t<ListView\n\t\t\tdoctype=\"Leave Application\"\n\t\t\t:pageTitle=\"__('Leave History')\"\n\t\t\t:tabButtons=\"TA"
  },
  {
    "path": "frontend/src/views/salary_slip/Dashboard.vue",
    "chars": 3533,
    "preview": "<template>\n\t<BaseLayout :pageTitle=\"__('Salary Slips')\">\n\t\t<template #body>\n\t\t\t<div class=\"flex flex-col items-center my"
  },
  {
    "path": "frontend/src/views/salary_slip/Detail.vue",
    "chars": 4037,
    "preview": "<template>\n\t<ion-page>\n\t\t<ion-content :fullscreen=\"true\">\n\t\t\t<FormView\n\t\t\t\tv-if=\"formFields.data\"\n\t\t\t\tdoctype=\"Salary Sl"
  },
  {
    "path": "frontend/tailwind.config.js",
    "chars": 655,
    "preview": "import frappeUIPreset from \"frappe-ui/src/tailwind/preset\"\nexport default {\n\tpresets: [frappeUIPreset],\n\tcontent: [\n\t\t\"."
  },
  {
    "path": "frontend/vite.config.js",
    "chars": 2955,
    "preview": "import { defineConfig } from \"vite\"\nimport vue from \"@vitejs/plugin-vue\"\nimport { VitePWA } from \"vite-plugin-pwa\"\nimpor"
  },
  {
    "path": "hrms/__init__.py",
    "chars": 241,
    "preview": "import frappe\n\n__version__ = \"17.0.0-dev\"\n\n\ndef refetch_resource(cache_key: str | list, user=None):\n\tfrappe.publish_real"
  },
  {
    "path": "hrms/api/__init__.py",
    "chars": 22058,
    "preview": "import frappe\nfrom frappe import _\nfrom frappe.model import get_permitted_fields\nfrom frappe.model.workflow import get_w"
  },
  {
    "path": "hrms/api/oauth.py",
    "chars": 902,
    "preview": "import frappe\n\n\n@frappe.whitelist(allow_guest=True)\ndef oauth_providers():\n\tfrom frappe.utils.html_utils import get_icon"
  },
  {
    "path": "hrms/api/roster.py",
    "chars": 8990,
    "preview": "import frappe\nfrom frappe import _\nfrom frappe.utils import add_days, date_diff\n\nfrom erpnext.setup.doctype.employee.emp"
  },
  {
    "path": "hrms/api/system_settings.py",
    "chars": 150,
    "preview": "import frappe\n\n\n@frappe.whitelist(allow_guest=True)\ndef get_user_pass_login_disabled():\n\treturn frappe.get_system_settin"
  },
  {
    "path": "hrms/config/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "hrms/config/desktop.py",
    "chars": 111,
    "preview": "from frappe import _\n\n\ndef get_data():\n\treturn [{\"module_name\": \"HRMS\", \"type\": \"module\", \"label\": _(\"HRMS\")}]\n"
  },
  {
    "path": "hrms/config/docs.py",
    "chars": 251,
    "preview": "\"\"\"\nConfiguration for docs\n\"\"\"\n\n# source_link = \"https://github.com/[org_name]/hrms\"\n# headline = \"App that does everyth"
  },
  {
    "path": "hrms/controllers/employee_boarding_controller.py",
    "chars": 5862,
    "preview": "# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors\n# License: GNU General Public License v3. See licen"
  },
  {
    "path": "hrms/controllers/employee_reminders.py",
    "chars": 9769,
    "preview": "# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors\n# License: GNU General Public License v3. See licen"
  },
  {
    "path": "hrms/controllers/tests/test_employee_reminders.py",
    "chars": 9332,
    "preview": "# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors\n# License: GNU General Public License v3. See licen"
  },
  {
    "path": "hrms/desktop_icon/expenses.json",
    "chars": 522,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.194261\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/frappe_hr.json",
    "chars": 438,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.167514\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/leaves.json",
    "chars": 519,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.197925\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/payroll.json",
    "chars": 529,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.192437\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/people.json",
    "chars": 542,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-19 13:17:44.832657\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/performance.json",
    "chars": 533,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.196211\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/recruitment.json",
    "chars": 534,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.202873\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/shift_&_attendance.json",
    "chars": 564,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.199386\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/tax_&_benefits.json",
    "chars": 552,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.190559\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/desktop_icon/tenure.json",
    "chars": 529,
    "preview": "{\n \"app\": \"hrms\",\n \"creation\": \"2025-11-17 20:33:43.201465\",\n \"docstatus\": 0,\n \"doctype\": \"Desktop Icon\",\n \"hidden\": 0,\n"
  },
  {
    "path": "hrms/hooks.py",
    "chars": 12406,
    "preview": "app_name = \"hrms\"\napp_title = \"Frappe HR\"\napp_publisher = \"Frappe Technologies Pvt. Ltd.\"\napp_description = \"Modern HR a"
  },
  {
    "path": "hrms/hr/README.md",
    "chars": 75,
    "preview": "Key features:\n\n- Leave and Attendance\n- Payroll\n- Appraisal\n- Expense Claim"
  },
  {
    "path": "hrms/hr/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "hrms/hr/dashboard_chart/appraisal_overview/appraisal_overview.json",
    "chars": 746,
    "preview": "{\n \"chart_name\": \"Appraisal Overview\",\n \"chart_type\": \"Report\",\n \"creation\": \"2026-01-10 14:50:41.278540\",\n \"currency\": "
  },
  {
    "path": "hrms/hr/dashboard_chart/attendance_count/attendance_count.json",
    "chars": 1104,
    "preview": "{\n \"chart_name\": \"Attendance Count\",\n \"chart_type\": \"Report\",\n \"creation\": \"2020-07-22 11:56:32.730068\",\n \"custom_option"
  },
  {
    "path": "hrms/hr/dashboard_chart/claims_by_type/claims_by_type.json",
    "chars": 819,
    "preview": "{\n \"based_on\": \"\",\n \"chart_name\": \"Claims by Type\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2022-08-31 23:04:43.377345\""
  },
  {
    "path": "hrms/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json",
    "chars": 891,
    "preview": "{\n \"chart_name\": \"Department Wise Employee Count\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2020-07-22 11:56:32.760730\","
  },
  {
    "path": "hrms/hr/dashboard_chart/department_wise_expense_claims/department_wise_expense_claims.json",
    "chars": 965,
    "preview": "{\n \"based_on\": \"\",\n \"chart_name\": \"Department wise Expense Claims\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2022-08-31 "
  },
  {
    "path": "hrms/hr/dashboard_chart/department_wise_openings/department_wise_openings.json",
    "chars": 940,
    "preview": "{\n \"aggregate_function_based_on\": \"planned_vacancies\",\n \"chart_name\": \"Department Wise Openings\",\n \"chart_type\": \"Group "
  },
  {
    "path": "hrms/hr/dashboard_chart/department_wise_timesheet_hours/department_wise_timesheet_hours.json",
    "chars": 1059,
    "preview": "{\n \"aggregate_function_based_on\": \"total_hours\",\n \"based_on\": \"\",\n \"chart_name\": \"Department wise Timesheet Hours\",\n \"ch"
  },
  {
    "path": "hrms/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json",
    "chars": 894,
    "preview": "{\n \"chart_name\": \"Designation Wise Employee Count\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2020-07-22 11:56:32.790337\""
  },
  {
    "path": "hrms/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json",
    "chars": 943,
    "preview": "{\n \"aggregate_function_based_on\": \"planned_vacancies\",\n \"chart_name\": \"Designation Wise Openings\",\n \"chart_type\": \"Group"
  },
  {
    "path": "hrms/hr/dashboard_chart/employee_advance_status/employee_advance_status.json",
    "chars": 956,
    "preview": "{\n \"based_on\": \"\",\n \"chart_name\": \"Employee Advance Status\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2022-08-31 23:06:1"
  },
  {
    "path": "hrms/hr/dashboard_chart/employees_by_age/employees_by_age.json",
    "chars": 929,
    "preview": "{\n \"based_on\": \"\",\n \"chart_name\": \"Employees by Age\",\n \"chart_type\": \"Custom\",\n \"creation\": \"2022-08-22 19:07:51.906347\""
  },
  {
    "path": "hrms/hr/dashboard_chart/employees_by_branch/employees_by_branch.json",
    "chars": 865,
    "preview": "{\n \"chart_name\": \"Employees by Branch\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2022-08-22 12:33:43.241006\",\n \"custom_o"
  },
  {
    "path": "hrms/hr/dashboard_chart/employees_by_grade/employees_by_grade.json",
    "chars": 862,
    "preview": "{\n \"chart_name\": \"Employees by Grade\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2022-08-22 12:33:23.767559\",\n \"custom_op"
  },
  {
    "path": "hrms/hr/dashboard_chart/employees_by_type/employees_by_type.json",
    "chars": 870,
    "preview": "{\n \"chart_name\": \"Employees by Type\",\n \"chart_type\": \"Group By\",\n \"creation\": \"2022-08-22 13:49:59.343893\",\n \"custom_opt"
  },
  {
    "path": "hrms/hr/dashboard_chart/expense_claims/expense_claims.json",
    "chars": 950,
    "preview": "{\n \"based_on\": \"posting_date\",\n \"chart_name\": \"Expense Claims\",\n \"chart_type\": \"Sum\",\n \"color\": \"#449CF0\",\n \"creation\": "
  }
]

// ... and 1251 more files (download for full content)

About this extraction

This page contains the full source code of the frappe/hrms GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1451 files (17.2 MB), approximately 4.6M tokens, and a symbol index with 3107 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!