From 2fee7c18bb7209bce80b30e1de3094a7946b72a1 Mon Sep 17 00:00:00 2001 From: Horilla <131998600+horilla-opensource@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:22:44 +0530 Subject: [PATCH] [IMP] Remove inter module dependency (#274) This commit introduces significant changes to the architecture of the Horilla HRMS system by decoupling interdependent modules. The following modifications were made: 1. **Module Independence**: Each module has been refactored to eliminate reliance on other modules, promoting a more modular and maintainable codebase. 2. **Refactored Imports and Dependencies**: Adjusted import statements and dependency injections to support independent module operation. 3. **Compatibility and Functionality**: Ensured that all modules are compatible with existing systems and maintain their intended functionality both independently and when integrated with other modules. These changes enhance the modularity, maintainability, and scalability of the Horilla HRMS, allowing developers to work on individual modules without affecting the entire system. Future development and deployment will be more efficient and less prone to issues arising from tightly coupled code. **NOTE** For existing Horilla users, if you face any issues during the migrations, please run the following command and try again the migrations. - `python3 manage.py makemigrations` - `python3 manage.py migrate base` - `python3 manage.py migrate` * [IMP] ASSET: Asset module dependency removal from other Horilla apps * [IMP] ATTENDANCE: Attendance module dependency removal from other Horilla apps * [IMP] BASE: Base module dependency removal from other Horilla apps * [IMP] EMPLOYEE: Employee module dependency removal from other Horilla apps * [IMP] HELPDESK: Helpdesk module dependency removal from other Horilla apps * [IMP] HORILLA AUDIT: Horilla Audit module dependency removal from other Horilla apps * [IMP] HORILLA CRUMBS: Horilla Crumbs module dependency removal from other Horilla apps * [IMP] HORILLA AUTOMATIONS: Horilla Automations module dependency removal from other Horilla apps * [IMP] HORILLA VIEWS: Horilla Views module dependency removal from other Horilla apps * [IMP] LEAVE: Leave module dependency removal from other Horilla apps * [IMP] OFFBOARDING: Offboarding module dependency removal from other Horilla apps * [IMP] ONBOARDING: Onboarding module dependency removal from other Horilla apps * [IMP] PMS: PMS module dependency removal from other Horilla apps * [IMP] PAYROLL: Payroll module dependency removal from other Horilla apps * [IMP] RECRUITMENT: Recruitment module dependency removal from other Horilla apps * [IMP] HORILLA: Dependency removal updates * [IMP] TEMPLATES: Dependency removal updates * [IMP] STATIC: Dependency removal updates * [IMP] HORILLA DOCUMENTS: Horilla Documents module dependency removal from other Horilla apps * [ADD] HORILLA: methods.py * [UPDT] HORILLA: Settings.py * [FIX] EMPLOYEE: About tab issue * Update horilla_settings.py * Remove dummy db init password --- asset/apps.py | 10 + asset/forms.py | 3 + asset/models.py | 4 +- asset/static/src/asset/dashboard.js | 3 +- asset/templates/asset/asset_information.html | 2 +- asset/templates/asset/asset_return_form.html | 141 +- asset/templates/asset_history/group_by.html | 2 +- .../request_allocation/group_by.html | 2 +- asset/urls.py | 16 + asset/views.py | 102 +- attendance/admin.py | 5 +- attendance/apps.py | 10 + attendance/filters.py | 12 +- attendance/forms.py | 67 +- attendance/methods/utils.py | 476 ++++ attendance/models.py | 549 +++-- attendance/sidebar.py | 9 + attendance/static/attendance/actions.js | 2 +- .../static/dashboard/attendanceChart.js | 79 +- .../attendance/dashboard/dashboard.html | 2 +- .../attendance/dashboard/overtime_table.html | 4 +- .../dashboard/to_validate_table.html | 2 +- .../grace_time/grace_time_table.html | 2 +- .../late_come_early_out/group_by.html | 6 +- .../late_come_early_out/report_list.html | 6 +- .../late_come_early_out/single_report.html | 4 +- .../templates/attendance/penalty/form.html | 206 +- .../work_record/work_record_create.html | 16 + .../work_record_employees_view.html | 74 + .../work_record/work_record_view copy.html | 37 + .../attendance/attendance_comment.html | 2 +- .../requests/attendance/comment_view.html | 4 +- attendance/urls.py | 100 +- attendance/views.py | 131 +- attendance/views/clock_in_out.py | 16 +- attendance/views/dashboard.py | 154 +- attendance/views/penalty.py | 47 +- attendance/views/requests.py | 17 +- attendance/views/search.py | 4 +- attendance/views/views.py | 569 +++-- base/admin.py | 6 + base/context_processors.py | 44 +- base/filters.py | 119 +- base/forms.py | 203 +- base/methods.py | 248 ++- base/migrations/__init__.py | 144 ++ base/models.py | 203 +- base/request_and_approve.py | 153 +- base/scheduler.py | 25 +- base/static/holiday/action.js | 384 ++++ .../announcement/announcement_one.html | 6 +- base/templates/announcement/comment_view.html | 4 +- base/templates/base/general_settings.html | 18 +- .../base/rotating_shift/htmx/group_by.html | 2 +- .../rotating_work_type/htmx/group_by.html | 2 +- base/templates/common_form.html | 49 + .../company_leave/company_leave.html | 127 ++ .../company_leave_creation_form.html | 56 + .../company_leave_update_form.html | 58 + .../company_leave/company_leave_view.html | 100 + base/templates/holiday/holiday.html | 262 +++ .../holiday/holiday_export_filter_form.html | 78 + base/templates/holiday/holiday_filter.html | 45 + base/templates/holiday/holiday_form.html | 70 + .../holiday/holiday_update_form.html | 63 + base/templates/holiday/holiday_view.html | 297 +++ base/templates/horilla_form.html | 56 + base/templates/mail/empty_mail_template.html | 89 + base/templates/mail/htmx/form.html | 65 + base/templates/mail/view_templates.html | 109 + .../condition_table.html | 8 +- base/templates/penalty/penalty_view.html | 42 + .../asset_requests_approve.html | 4 +- .../attendance_validate.html | 4 +- .../request_and_approve/feedback_answer.html | 4 +- .../leave_allocation_approve.html | 4 +- .../leave_request_approve.html | 4 +- .../request_and_approve/overtime_approve.html | 4 +- .../request_and_approve/shift_request.html | 4 +- .../work_type_request.html | 4 +- .../htmx/allocation_details.html | 2 +- .../htmx/allocation_requests.html | 2 +- .../shift_request/htmx/comment_view.html | 4 +- .../shift_request/htmx/group_by.html | 2 +- .../shift_request/htmx/requests.html | 2 +- .../shift_request/htmx/shift_comment.html | 2 +- .../htmx/shift_request_detail.html | 2 +- .../shift_request/request_update_form.html | 2 +- .../shift_request/shift_request.html | 2 +- .../shift_request/shift_request_export.html | 2 +- .../shift_request/shift_request_view.html | 2 +- .../work_type_request/htmx/comment_view.html | 4 +- .../work_type_request/htmx/group_by.html | 2 +- .../work_type_request/htmx/requests.html | 2 +- .../htmx/work_type_comment.html | 2 +- .../htmx/work_type_request_single_view.html | 2 +- .../request_update_form.html | 2 +- base/templatetags/basefilters.py | 33 +- base/templatetags/horillafilters.py | 288 +++ base/urls.py | 197 +- base/views.py | 1100 +++++---- employee/filters.py | 76 +- employee/forms.py | 17 + employee/models.py | 131 +- employee/not_in_out_dashboard.py | 8 +- employee/templates/dashboard/not_in_yet.html | 4 +- employee/templates/dashboard/not_out_yet.html | 3 +- .../dashboard/upcoming_birthdays.html | 2 +- .../disciplinary_nav.html | 10 - .../disciplinary_records.html | 2 +- .../documents/document_requests.html | 2 +- employee/templates/documents/requests.html | 2 +- .../dashboard/dashboard_employee.html | 4 +- .../employee/profile/profile_view.html | 353 ++- .../employee/update_form/work_details.html | 8 +- .../templates/employee/view/individual.html | 872 ++++---- .../templates/employee_export_filter.html | 213 +- employee/templates/employee_nav.html | 47 +- .../employee_personal_info/bulk_update.html | 4 +- .../employee_personal_info/employee_card.html | 16 +- .../employee_personal_info.html | 149 +- .../employee_update_form.html | 2 +- .../employee_personal_info/group_by.html | 2 +- .../employee_personal_info/script.js | 2 +- .../employee_personal_info/style.css | 2 +- employee/templates/import_export_nav.html | 2 +- employee/templates/policies/form.html | 27 +- employee/templates/policies/nav.html | 12 +- employee/templates/policies/records.html | 4 +- employee/templates/policies/view_policy.html | 21 +- .../tabs/allowance_deduction-tab.html | 2 +- employee/templates/tabs/attendance-tab.html | 6 +- employee/templates/tabs/contract-tab.html | 6 +- employee/templates/tabs/htmx/view_file.html | 2 +- employee/templates/tabs/leave-tab.html | 6 +- employee/templates/tabs/penalty_account.html | 4 +- employee/templates/tabs/personal-tab.html | 296 +-- .../templates/tabs/scheduled_interview.html | 2 +- employee/templates/tabs/shift-tab.html | 4 +- employee/urls.py | 42 +- employee/views.py | 521 ++--- helpdesk/apps.py | 10 + helpdesk/forms.py | 4 +- .../templates/helpdesk/faq/faq_list_view.html | 5 +- .../helpdesk/ticket/ticket_form.html | 17 +- .../helpdesk/ticket/ticket_group.html | 2 +- .../helpdesk/ticket/ticket_view.html | 3 +- helpdesk/urls.py | 17 + helpdesk/views.py | 95 + horilla/config.py | 69 +- horilla/decorators.py | 1 - horilla/filters.py | 66 +- horilla/horilla_apps.py | 6 +- horilla/methods.py | 45 + horilla/models.py | 49 +- horilla/settings.py | 90 +- horilla/signals.py | 2 +- horilla/urls.py | 10 - .../horilla_audit/history_tracking.html | 10 +- .../horilla_audit/horilla_audit_log.html | 94 +- horilla_automations/apps.py | 15 +- horilla_automations/methods/methods.py | 20 +- horilla_automations/models.py | 11 +- horilla_automations/signals.py | 1 + horilla_crumbs/context_processors.py | 11 +- horilla_documents/models.py | 31 +- horilla_views/admin.py | 12 +- horilla_views/cbv_methods.py | 75 +- horilla_views/forms.py | 41 - horilla_views/generic/cbv/views.py | 311 +-- horilla_views/models.py | 35 - .../templates/generic/filter_tags.html | 21 +- horilla_views/templates/generic/form.html | 36 +- horilla_views/templates/generic/group_by.html | 56 +- .../templates/generic/horilla_card.html | 50 +- .../generic/horilla_detailed_view.html | 10 +- .../templates/generic/horilla_form.html | 4 +- .../templates/generic/horilla_list.html | 55 +- .../templates/generic/horilla_nav.html | 13 +- .../templates/generic/horilla_tabs.html | 6 +- .../templatetags/generic_template_filters.py | 45 +- horilla_views/urls.py | 16 - horilla_views/views.py | 91 +- leave/admin.py | 7 +- leave/apps.py | 10 + leave/filters.py | 244 +- leave/forms.py | 346 ++- leave/methods.py | 93 +- leave/migrations/__init__.py | 8 - leave/models.py | 233 +- leave/scheduler.py | 22 - leave/sidebar.py | 14 +- leave/static/dashboard/dashboard.js | 939 ++++---- .../compensatory_leave_comment.html | 6 +- leave/templates/leave/dashboard.html | 2 +- leave/templates/leave/employee_dashboard.html | 2 +- .../comment_view.html | 4 +- .../leave_allocation_comment.html | 2 +- .../leave_allocation_request_group_by.html | 2 +- .../leave_allocation_request_single_view.html | 4 +- .../leave/leave_assign/group_by.html | 2 +- .../leave_assign/single_assign_view.html | 2 +- .../leave/leave_request/comment_view.html | 4 +- .../dashboard_leave_requests.html | 2 +- .../leave/leave_request/group_by.html | 14 +- .../leave/leave_request/leave_comment.html | 2 +- .../leave_request/leave_request_form.html | 117 +- .../leave/leave_request/leave_requests.html | 24 +- .../leave/leave_request/one_request_view.html | 2 +- .../leave_request/request_update_form.html | 118 +- .../leave_type_individual_view.html | 2 +- .../templates/leave/user_leave/group_by.html | 2 +- .../leave/user_leave/request_form.html | 118 +- .../leave/user_leave/user_leave.html | 2 +- .../leave/user_leave/user_request_form.html | 122 +- .../leave/user_leave/user_request_update.html | 118 +- leave/urls.py | 225 +- leave/views.py | 1981 ++++++++--------- offboarding/apps.py | 10 + offboarding/forms.py | 53 - offboarding/models.py | 14 +- .../offboarding/note/view_notes.html | 2 +- .../offboarding/resignation/request_list.html | 2 +- .../offboarding/task/table_body.html | 2 +- offboarding/views.py | 50 +- onboarding/apps.py | 10 + onboarding/forms.py | 3 + onboarding/static/dashboard/onboardChart.js | 51 - .../static/onboarding_view/dashboard.js | 216 +- .../onboarding/dashboard/task_report.html | 4 +- onboarding/urls.py | 5 - onboarding/views.py | 53 +- payroll/admin.py | 3 +- payroll/apps.py | 7 + payroll/forms/component_forms.py | 43 +- payroll/forms/forms.py | 15 - payroll/methods/methods.py | 280 +-- payroll/methods/payslip_calc.py | 29 +- payroll/models/models.py | 350 +-- payroll/static/payroll/action.js | 2 +- payroll/static/payroll/dashboard.js | 102 +- .../templates/payroll/contract/group_by.html | 2 +- payroll/templates/payroll/dashboard.html | 2 +- .../payroll/payslip/create_payslip.html | 25 - .../payroll/payslip/view_payslips.html | 9 +- .../payroll/reimbursement/comment_view.html | 4 +- .../reimbursement/reimbursement_comment.html | 2 +- .../reimbursement/reimbursement_list.html | 5 +- .../payroll/reimbursement/request_cards.html | 5 +- payroll/urls/component_urls.py | 5 - payroll/urls/urls.py | 6 - payroll/views/component_views.py | 70 +- payroll/views/views.py | 69 - payroll/widgets/component_widgets.py | 16 +- pms/apps.py | 10 + pms/forms.py | 9 +- pms/models.py | 2 +- pms/templates/okr/key_result/kr_card.html | 3 +- pms/templates/okr/key_result/kr_list.html | 2 +- pms/templates/okr/key_result/view_kr.html | 2 +- pms/templatetags/pmsfilters.py | 29 + pms/urls.py | 6 + pms/views.py | 82 +- recruitment/admin.py | 2 - recruitment/apps.py | 10 + recruitment/forms.py | 106 +- .../static/dashboard/recruitmentChart.js | 51 + .../templates/candidate/application_form.html | 10 +- .../templates/candidate/candidate_card.html | 50 +- .../templates/candidate/candidate_list.html | 36 +- .../candidate/candidate_update_form.html | 2 +- recruitment/templates/candidate/group_by.html | 36 +- .../templates/candidate/individual.html | 6 +- recruitment/templates/candidate/success.html | 2 +- .../dashboard/candidates_per_stage.html | 2 +- .../templates/dashboard/dashboard.html | 208 +- .../components/candidate_stage_component.html | 28 +- .../candidate_kanban_components.html | 46 +- .../templates/pipeline/pipeline_card.html | 46 +- .../pipeline_components/add_note.html | 2 +- .../pipeline_components/view_note.html | 2 +- .../templates/pipeline/pipeline_empty.html | 2 +- .../templates/pipeline/pipeline_tab.html | 2 +- .../recruitment/recruitment_form.html | 2 +- recruitment/templates/select2.js | 2 +- recruitment/templates/stage/nav.html | 2 +- .../question_template_organized_form.html | 2 +- .../survey/view_single_template.html | 25 +- .../templatetags/recruitmentfilters.py | 10 +- recruitment/urls.py | 52 +- recruitment/views/actions.py | 23 + recruitment/views/mail_templates.py | 99 +- recruitment/views/surveys.py | 33 +- recruitment/views/views.py | 91 +- recruitment/widgets.py | 6 +- static/build/css/style.min.css | 4 +- static/build/js/htmxSelect2.js | 61 +- templates/404.html | 2 +- templates/405.html | 2 +- templates/dashboard.html | 1818 +++++++-------- templates/floating_button.html | 67 +- templates/index.html | 155 +- templates/navbar.html | 2 +- templates/no_perm.html | 2 +- templates/reset_send.html | 2 +- templates/settings.html | 225 +- templates/sidebar.html | 18 +- templates/went_wrong.html | 2 +- 308 files changed, 12414 insertions(+), 9577 deletions(-) create mode 100644 attendance/methods/utils.py create mode 100644 attendance/templates/attendance/work_record/work_record_create.html create mode 100644 attendance/templates/attendance/work_record/work_record_employees_view.html create mode 100644 attendance/templates/attendance/work_record/work_record_view copy.html create mode 100644 base/static/holiday/action.js create mode 100644 base/templates/common_form.html create mode 100644 base/templates/company_leave/company_leave.html create mode 100644 base/templates/company_leave/company_leave_creation_form.html create mode 100644 base/templates/company_leave/company_leave_update_form.html create mode 100644 base/templates/company_leave/company_leave_view.html create mode 100644 base/templates/holiday/holiday.html create mode 100644 base/templates/holiday/holiday_export_filter_form.html create mode 100644 base/templates/holiday/holiday_filter.html create mode 100644 base/templates/holiday/holiday_form.html create mode 100644 base/templates/holiday/holiday_update_form.html create mode 100644 base/templates/holiday/holiday_view.html create mode 100644 base/templates/horilla_form.html create mode 100644 base/templates/mail/empty_mail_template.html create mode 100644 base/templates/mail/htmx/form.html create mode 100644 base/templates/mail/view_templates.html create mode 100644 base/templates/penalty/penalty_view.html create mode 100644 base/templatetags/horillafilters.py create mode 100644 horilla/methods.py diff --git a/asset/apps.py b/asset/apps.py index 9c0c19427..ab042f3bc 100644 --- a/asset/apps.py +++ b/asset/apps.py @@ -18,3 +18,13 @@ class AssetConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "asset" + + def ready(self): + from django.urls import include, path + + from horilla.urls import urlpatterns + + urlpatterns.append( + path("asset/", include("asset.urls")), + ) + super().ready() diff --git a/asset/forms.py b/asset/forms.py index 9faa18f57..652ebb33c 100644 --- a/asset/forms.py +++ b/asset/forms.py @@ -411,6 +411,9 @@ class AssetReturnForm(ModelForm): } def __init__(self, *args, **kwargs): + """ + Initializes the AssetReturnForm with initial values and custom field settings. + """ super(AssetReturnForm, self).__init__(*args, **kwargs) self.fields["return_date"].initial = date.today() diff --git a/asset/models.py b/asset/models.py index eeb871d36..fcd31a8c6 100644 --- a/asset/models.py +++ b/asset/models.py @@ -210,7 +210,9 @@ class AssetRequest(HorillaModel): null=False, blank=False, ) - asset_category_id = models.ForeignKey(AssetCategory, on_delete=models.PROTECT) + asset_category_id = models.ForeignKey( + AssetCategory, on_delete=models.PROTECT, verbose_name=_("Asset Category") + ) asset_request_date = models.DateField(auto_now_add=True) description = models.TextField(null=True, blank=True, max_length=255) asset_request_status = models.CharField( diff --git a/asset/static/src/asset/dashboard.js b/asset/static/src/asset/dashboard.js index 6d6452ed7..c8179c278 100644 --- a/asset/static/src/asset/dashboard.js +++ b/asset/static/src/asset/dashboard.js @@ -1,3 +1,4 @@ +staticUrl = $("#statiUrl").attr("data-url"); $(document).ready(function() { function available_asset_chart(dataSet) { var Asset_available_chart = document.getElementById("assetAvailableChart"); @@ -83,7 +84,7 @@ function emptyAssetAvialabeChart(assetAvailableChartChart,args,options){ var noDataImage = new Image(); noDataImage.src = assetAvailableChartChart.data.emptyImageSrc ? assetAvailableChartChart.data.emptyImageSrc - : "/static/images/ui/joiningchart.png"; + : staticUrl +"images/ui/joiningchart.png"; message = assetAvailableChartChart.data.message ? assetAvailableChartChart.data.message diff --git a/asset/templates/asset/asset_information.html b/asset/templates/asset/asset_information.html index 74954c715..542da05b9 100644 --- a/asset/templates/asset/asset_information.html +++ b/asset/templates/asset/asset_information.html @@ -117,4 +117,4 @@ - + diff --git a/asset/templates/asset/asset_return_form.html b/asset/templates/asset/asset_return_form.html index b7d80cec0..0a8a2c2b9 100644 --- a/asset/templates/asset/asset_return_form.html +++ b/asset/templates/asset/asset_return_form.html @@ -1,60 +1,89 @@ -{% load i18n %} +{% load i18n %} {% load horillafilters %}
- - -
{% trans "Asset Return Form" %}
-
+ + +
{% trans "Asset Return Form" %}
+
-
- {% csrf_token %} -
-
- - {{asset_return_form.return_status}} -
-
- - {{asset_return_form.return_date}} - {{asset_return_form.return_date.errors}} -
-
- - {{asset_return_form.return_condition}} -
-
- - {{asset_return_form.return_images}} -
-
- - {% if perms.payroll.add_loanaccount %} - - {% endif %} - -
-
-
+
+ {% csrf_token %} +
+
+ + {{asset_return_form.return_status}} +
+
+ + {{asset_return_form.return_date}} + {{asset_return_form.return_date.errors}} +
+
+ + {{asset_return_form.return_condition}} +
+
+ + {{asset_return_form.return_images}} +
+
+ + {% if "payroll"|app_installed %} + {% if perms.payroll.add_loanaccount %} + + {% endif %} + {% endif %} + +
+
+
diff --git a/asset/templates/asset_history/group_by.html b/asset/templates/asset_history/group_by.html index 3105ed6e6..810d800d7 100644 --- a/asset/templates/asset_history/group_by.html +++ b/asset/templates/asset_history/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %}
{% for asset_history_list in asset_assignments %} diff --git a/asset/templates/request_allocation/group_by.html b/asset/templates/request_allocation/group_by.html index 2ed210612..d81ab5cd9 100644 --- a/asset/templates/request_allocation/group_by.html +++ b/asset/templates/request_allocation/group_by.html @@ -1,5 +1,5 @@ {% load i18n %} -{% include 'filter_tags.html' %}{% load attendancefilters %} +{% include 'filter_tags.html' %}{% load horillafilters %}
diff --git a/asset/urls.py b/asset/urls.py index c87b71efc..0bb091029 100644 --- a/asset/urls.py +++ b/asset/urls.py @@ -178,4 +178,20 @@ urlpatterns = [ views.asset_history_search, name="asset-history-search", ), + path("asset-tab/", views.asset_tab, name="asset-tab"), + path( + "profile-asset-tab/", + views.profile_asset_tab, + name="profile-asset-tab", + ), + path( + "asset-request-tab/", + views.asset_request_tab, + name="asset-request-tab", + ), + path( + "dashboard-asset-request-approve", + views.dashboard_asset_request_approve, + name="dashboard-asset-request-approve", + ), ] diff --git a/asset/views.py b/asset/views.py index 8933ac589..594032a07 100644 --- a/asset/views.py +++ b/asset/views.py @@ -55,11 +55,13 @@ from base.methods import ( ) from base.models import Company from base.views import paginator_qry -from employee.models import EmployeeWorkInformation +from employee.models import Employee, EmployeeWorkInformation +from horilla import settings from horilla.decorators import ( hx_request_required, login_required, manager_can_enter, + owner_can_enter, permission_required, ) from horilla.group_by import group_by_queryset @@ -1422,7 +1424,7 @@ def asset_available_chart(request): "labels": labels, "dataset": dataset, "message": _("Oops!! No Asset found..."), - "emptyImageSrc": "/static/images/ui/asset.png", + "emptyImageSrc": f"/{settings.STATIC_URL}images/ui/asset.png", } return JsonResponse(response) @@ -1452,7 +1454,7 @@ def asset_category_chart(request): "labels": labels, "dataset": dataset, "message": _("Oops!! No Asset found..."), - "emptyImageSrc": "/static/images/ui/asset.png", + "emptyImageSrc": f"/{settings.STATIC_URL}images/ui/asset.png", } return JsonResponse(response) @@ -1568,3 +1570,97 @@ def asset_history_search(request): "requests_ids": requests_ids, }, ) + + +@login_required +@owner_can_enter("asset.view_asset", Employee) +def asset_tab(request, emp_id): + """ + This function is used to view asset tab of an employee in employee individual view. + + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. + + Returns: return asset-tab template + + """ + employee = Employee.objects.get(id=emp_id) + assets_requests = employee.requested_employee.all() + assets = employee.allocated_employee.all() + assets_ids = ( + json.dumps([instance.id for instance in assets]) if assets else json.dumps([]) + ) + context = { + "assets": assets, + "requests": assets_requests, + "assets_ids": assets_ids, + "employee": emp_id, + } + return render(request, "tabs/asset-tab.html", context=context) + + +@login_required +@hx_request_required +def profile_asset_tab(request, emp_id): + """ + This function is used to view asset tab of an employee in employee profile view. + + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. + + Returns: return profile-asset-tab template + + """ + employee = Employee.objects.get(id=emp_id) + assets = employee.allocated_employee.all() + assets_ids = json.dumps([instance.id for instance in assets]) + context = { + "assets": assets, + "assets_ids": assets_ids, + } + return render(request, "tabs/profile-asset-tab.html", context=context) + + +@login_required +@hx_request_required +def asset_request_tab(request, emp_id): + """ + This function is used to view asset request tab of an employee in employee individual view. + + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. + + Returns: return asset-request-tab template + + """ + employee = Employee.objects.get(id=emp_id) + assets_requests = employee.requested_employee.all() + context = {"asset_requests": assets_requests, "emp_id": emp_id} + return render(request, "tabs/asset-request-tab.html", context=context) + + +@login_required +def dashboard_asset_request_approve(request): + + asset_requests = AssetRequest.objects.filter( + asset_request_status="Requested", requested_employee_id__is_active=True + ) + asset_requests = filtersubordinates( + request, + asset_requests, + "asset.change_assetrequest", + field="requested_employee_id", + ) + requests_ids = json.dumps([instance.id for instance in asset_requests]) + + return render( + request, + "request_and_approve/asset_requests_approve.html", + { + "asset_requests": asset_requests, + "requests_ids": requests_ids, + }, + ) diff --git a/attendance/admin.py b/attendance/admin.py index 32114ecab..67b561010 100644 --- a/attendance/admin.py +++ b/attendance/admin.py @@ -4,6 +4,7 @@ admin.py This page is used to register attendance models with admins site. """ +from django.apps import apps from django.contrib import admin from .models import ( @@ -14,7 +15,7 @@ from .models import ( AttendanceRequestComment, AttendanceValidationCondition, GraceTime, - PenaltyAccount, + WorkRecords, ) # Register your models here. @@ -23,6 +24,6 @@ admin.site.register(AttendanceActivity) admin.site.register(AttendanceOverTime) admin.site.register(AttendanceLateComeEarlyOut) admin.site.register(AttendanceValidationCondition) -admin.site.register(PenaltyAccount) admin.site.register(GraceTime) admin.site.register(AttendanceRequestComment) +admin.site.register(WorkRecords) diff --git a/attendance/apps.py b/attendance/apps.py index ed1c78cee..5608eecc0 100644 --- a/attendance/apps.py +++ b/attendance/apps.py @@ -4,3 +4,13 @@ from django.apps import AppConfig class AttendanceConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "attendance" + + def ready(self): + from django.urls import include, path + + from horilla.urls import urlpatterns + + urlpatterns.append( + path("attendance/", include("attendance.urls")), + ) + super().ready() diff --git a/attendance/filters.py b/attendance/filters.py index 1c4354bce..fc135987a 100644 --- a/attendance/filters.py +++ b/attendance/filters.py @@ -9,6 +9,7 @@ import uuid import django_filters from django import forms +from django.apps import apps from django.db.models import OuterRef, Subquery from django.forms import DateTimeInput from django.utils.translation import gettext_lazy as _ @@ -18,7 +19,6 @@ from attendance.models import ( AttendanceActivity, AttendanceLateComeEarlyOut, AttendanceOverTime, - PenaltyAccount, strtime_seconds, ) from base.filters import FilterSet @@ -246,16 +246,6 @@ class LateComeEarlyOutFilter(FilterSet): self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" -class PenaltyFilter(FilterSet): - """ - PenaltyFilter - """ - - class Meta: - model = PenaltyAccount - fields = "__all__" - - class AttendanceActivityFilter(FilterSet): """ Filter set class for AttendanceActivity model diff --git a/attendance/forms.py b/attendance/forms.py index ec865e019..b0266ce7b 100644 --- a/attendance/forms.py +++ b/attendance/forms.py @@ -30,7 +30,9 @@ from collections import OrderedDict from typing import Any, Dict from django import forms +from django.apps import apps from django.core.exceptions import ValidationError +from django.db.models.query import QuerySet from django.forms import DateTimeInput from django.template.loader import render_to_string from django.utils.html import format_html @@ -46,22 +48,19 @@ from attendance.models import ( AttendanceRequestFile, AttendanceValidationCondition, GraceTime, - PenaltyAccount, + WorkRecords, attendance_date_validate, strtime_seconds, validate_time_format, ) from base.forms import MultipleFileField -from base.methods import reload_queryset +from base.methods import get_working_days, reload_queryset from base.models import Company, EmployeeShift from employee.filters import EmployeeFilter from employee.models import Employee from horilla import horilla_middlewares from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget -from leave.filters import LeaveRequestFilter -from leave.models import LeaveType -from payroll.methods.methods import get_working_days logger = logging.getLogger(__name__) @@ -756,27 +755,6 @@ class AttendanceExportForm(forms.Form): ) -class PenaltyAccountForm(ModelForm): - """ - PenaltyAccountForm - """ - - class Meta: - model = PenaltyAccount - fields = "__all__" - exclude = ["is_active"] - - def __init__(self, *args, **kwargs): - employee = kwargs.pop("employee", None) - super().__init__(*args, **kwargs) - if employee: - available_leaves = employee.available_leave.all() - assigned_leave_types = LeaveType.objects.filter( - id__in=available_leaves.values_list("leave_type_id", flat=True) - ) - self.fields["leave_type_id"].queryset = assigned_leave_types - - class LateComeEarlyOutExportForm(forms.Form): model_fields = AttendanceLateComeEarlyOut._meta.get_fields() field_choices_1 = [ @@ -889,15 +867,20 @@ def get_date_list(employee_id, from_date, to_date): attendance_dates = [] if len(working_date_list) > 0: # filter through approved leave of employee - approved_leave_dates_filtered = LeaveRequestFilter( - data={ - "from_date": working_date_list[0], - "to_date": working_date_list[-1], - "employee_id": employee_id, - "status": "approved", - } - ) - approved_leave_dates_filtered = approved_leave_dates_filtered.qs + if apps.is_installed("leave"): + from leave.filters import LeaveRequestFilter + + approved_leave_dates_filtered = LeaveRequestFilter( + data={ + "from_date": working_date_list[0], + "to_date": working_date_list[-1], + "employee_id": employee_id, + "status": "approved", + } + ) + approved_leave_dates_filtered = approved_leave_dates_filtered.qs + else: + approved_leave_dates_filtered = QuerySet().none() approved_leave_dates = [] # Extract the list of approved leave dates if len(approved_leave_dates_filtered) > 0: @@ -1078,3 +1061,17 @@ class BulkAttendanceRequestForm(ModelForm): instance.save() return instance + + +class WorkRecordsForm(ModelForm): + """ + WorkRecordForm + """ + + class Meta: + """ + Meta class for additional options + """ + + fields = "__all__" + model = WorkRecords diff --git a/attendance/methods/utils.py b/attendance/methods/utils.py new file mode 100644 index 000000000..ec06ca1c8 --- /dev/null +++ b/attendance/methods/utils.py @@ -0,0 +1,476 @@ +""" +utils.py + +This module is used write custom methods +""" + +import calendar +from datetime import datetime, timedelta + +from django.db import models +from django.db.models import Q, Sum +from django.http import HttpResponse +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ +from django.core.paginator import Paginator + +from base.models import WEEK_DAYS, CompanyLeaves, Holidays +from base.methods import get_pagination + +MONTH_MAPPING = { + "january": 1, + "february": 2, + "march": 3, + "april": 4, + "may": 5, + "june": 6, + "july": 7, + "august": 8, + "september": 9, + "october": 10, + "november": 11, + "december": 12, +} + + +def format_time(seconds): + """ + this method is used to formate seconds to H:M and return it + args: + seconds : seconds + """ + + hour = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + seconds = int((seconds % 3600) % 60) + return f"{hour:02d}:{minutes:02d}" + + +def strtime_seconds(time): + """ + this method is used reconvert time in H:M formate string back to seconds and return it + args: + time : time in H:M format + """ + + ftr = [3600, 60, 1] + return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) + + +def get_diff_obj(first_instance, other_instance, exclude_fields=None): + """ + Compare the fields of two instances and identify the changes. + + Args: + first_instance: The first instance to compare. + other_instance: The second instance to compare. + exclude_fields: A list of field names to exclude from comparison (optional). + + Returns: + A dictionary of changed fields with their old and new values. + """ + difference = {} + + fields_to_compare = first_instance._meta.fields + + if exclude_fields: + fields_to_compare = [ + field for field in fields_to_compare if field.name not in exclude_fields + ] + + for field in fields_to_compare: + old_value = getattr(first_instance, field.name) + new_value = getattr(other_instance, field.name) + + if old_value != new_value: + difference[field.name] = (old_value, new_value) + + return difference + + +def get_diff_dict(first_dict, other_dict, model=None): + """ + Compare two dictionaries and identify differing key-value pairs. + + Args: + first_dict: The first dictionary to compare. + other_dict: The second dictionary to compare. + model: The model class + + Returns: + A dictionary of differing keys with their old and new values. + """ + # model is passed as argument if any need of verbose name instead of field name + difference = {} + if model is None: + for key in first_dict: + if first_dict[key] != other_dict[key]: + # get the verbose name of the field + difference[key] = (first_dict[key], other_dict[key]) + return difference + for key in first_dict: + if first_dict[key] != other_dict[key]: + # get the verbose name of the field + field = model._meta.get_field(key) + verb_key = field.verbose_name + value = first_dict[key] + other_value = other_dict[key] + if isinstance(field, models.DateField): + if value is not None and value != "None": + value = datetime.strptime(value, "%Y-%m-%d").strftime("%d %b %Y") + if other_value is not None and other_value != "None": + other_value = datetime.strptime(other_value, "%Y-%m-%d").strftime( + "%d %b %Y" + ) + elif isinstance(field, models.TimeField): + if value is not None and value != "None": + if len(value.split(":")) == 2: + value = value + ":00" + value = datetime.strptime(value, "%H:%M:%S").strftime("%I:%M %p") + if other_value is not None and value != "None": + if len(other_value.split(":")) == 2: + other_value = other_value + ":00" + if other_value != "None": + other_value = datetime.strptime( + other_value, "%H:%M:%S" + ).strftime("%I:%M %p") + else: + other_value = "None" + elif isinstance(field, models.ForeignKey): + if value is not None and len(str(value)): + value = field.related_model.objects.get(id=value) + if other_value is not None and len(str(other_value)): + other_value = field.related_model.objects.get(id=other_value) + difference[verb_key] = (value, other_value) + return difference + + +def employee_exists(request): + """ + This method return the employee instance and work info if not exists return None instead + """ + employee, employee_work_info = None, None + try: + employee = request.user.employee_get + employee_work_info = employee.employee_work_info + finally: + return (employee, employee_work_info) + + +def shift_schedule_today(day, shift): + """ + This function is used to find shift schedules for the day, + it will returns min hour,start time seconds end time seconds + args: + shift : shift instance + day : shift day object + """ + schedule_today = day.day_schedule.filter(shift_id=shift) + start_time_sec, end_time_sec, minimum_hour = 0, 0, "00:00" + if schedule_today.exists(): + schedule_today = schedule_today[0] + minimum_hour = schedule_today.minimum_working_hour + start_time_sec = strtime_seconds(schedule_today.start_time.strftime("%H:%M")) + end_time_sec = strtime_seconds(schedule_today.end_time.strftime("%H:%M")) + return (minimum_hour, start_time_sec, end_time_sec) + + +def overtime_calculation(attendance): + """ + This method is used to calculate overtime of the attendance, it will + return difference between attendance worked hour and minimum hour if + and only worked hour greater than minimum hour, else return 00:00 + args: + attendance : attendance instance + """ + + minimum_hour = attendance.minimum_hour + at_work = attendance.attendance_worked_hour + at_work_sec = strtime_seconds(at_work) + minimum_hour_sec = strtime_seconds(minimum_hour) + if at_work_sec > minimum_hour_sec: + return format_time((at_work_sec - minimum_hour_sec)) + return "00:00" + + +def is_reportingmanger(request, instance): + """ + if the instance have employee id field then you can use this method to know the + request user employee is the reporting manager of the instance + args : + request : request + instance : an object or instance of any model contain employee_id foreign key field + """ + + manager = request.user.employee_get + try: + employee_workinfo_manager = ( + instance.employee_id.employee_work_info.reporting_manager_id + ) + except Exception: + return HttpResponse("This Employee Dont Have any work information") + return manager == employee_workinfo_manager + + +def validate_hh_mm_ss_format(value): + timeformat = "%H:%M:%S" + try: + validtime = datetime.strptime(value, timeformat) + return validtime.time() # Return the time object if needed + except ValueError as e: + raise ValidationError(_("Invalid format, it should be HH:MM:SS format")) + + +def validate_time_format(value): + """ + this method is used to validate the format of duration like fields. + """ + if len(value) > 6: + raise ValidationError(_("Invalid format, it should be HH:MM format")) + try: + hour, minute = value.split(":") + if len(hour) > 3 or len(minute) > 2: + raise ValidationError(_("Invalid time")) + hour = int(hour) + minute = int(minute) + if len(str(hour)) > 3 or len(str(minute)) > 2 or minute not in range(60): + raise ValidationError(_("Invalid time, excepted MM:SS")) + except ValueError as error: + raise ValidationError(_("Invalid format")) from error + + +def attendance_date_validate(date): + """ + Validates if the provided date is not a future date. + + :param date: The date to validate. + :raises ValidationError: If the provided date is in the future. + """ + today = datetime.today().date() + if not date: + raise ValidationError(_("Check date format.")) + elif date > today: + raise ValidationError(_("You cannot choose a future date.")) + + +def activity_datetime(attendance_activity): + """ + This method is used to convert clock-in and clock-out of activity as datetime object + args: + attendance_activity : attendance activity instance + """ + + # in + in_year = attendance_activity.clock_in_date.year + in_month = attendance_activity.clock_in_date.month + in_day = attendance_activity.clock_in_date.day + in_hour = attendance_activity.clock_in.hour + in_minute = attendance_activity.clock_in.minute + # out + out_year = attendance_activity.clock_out_date.year + out_month = attendance_activity.clock_out_date.month + out_day = attendance_activity.clock_out_date.day + out_hour = attendance_activity.clock_out.hour + out_minute = attendance_activity.clock_out.minute + return datetime(in_year, in_month, in_day, in_hour, in_minute), datetime( + out_year, out_month, out_day, out_hour, out_minute + ) + + +def get_week_start_end_dates(week): + """ + This method is use to return the start and end date of the week + """ + # Parse the ISO week date + year, week_number = map(int, week.split("-W")) + + # Get the date of the first day of the week + start_date = datetime.strptime(f"{year}-W{week_number}-1", "%Y-W%W-%w").date() + + # Calculate the end date by adding 6 days to the start date + end_date = start_date + timedelta(days=6) + + return start_date, end_date + + +def get_month_start_end_dates(year_month): + """ + This method is use to return the start and end date of the month + """ + # split year and month separately + year, month = map(int, year_month.split("-")) + # Get the first day of the month + start_date = datetime(year, month, 1).date() + + # Get the last day of the month + _, last_day = calendar.monthrange(year, month) + end_date = datetime(year, month, last_day).date() + + return start_date, end_date + + +def worked_hour_data(labels, records): + """ + To find all the worked hours + """ + data = { + "label": "Worked Hours", + "backgroundColor": "rgba(75, 192, 192, 0.6)", + } + dept_records = [] + for dept in labels: + total_sum = records.filter( + employee_id__employee_work_info__department_id__department=dept + ).aggregate(total_sum=Sum("hour_account_second"))["total_sum"] + dept_records.append(total_sum / 3600 if total_sum else 0) + data["data"] = dept_records + return data + + +def pending_hour_data(labels, records): + """ + To find all the pending hours + """ + data = { + "label": "Pending Hours", + "backgroundColor": "rgba(255, 99, 132, 0.6)", + } + dept_records = [] + for dept in labels: + total_sum = records.filter( + employee_id__employee_work_info__department_id__department=dept + ).aggregate(total_sum=Sum("hour_pending_second"))["total_sum"] + dept_records.append(total_sum / 3600 if total_sum else 0) + data["data"] = dept_records + return data + + +def get_employee_last_name(attendance): + """ + This method is used to return the last name + """ + if attendance.employee_id.employee_last_name: + return attendance.employee_id.employee_last_name + return "" + + +def attendance_day_checking(attendance_date, minimum_hour): + # Convert the string to a datetime object + attendance_datetime = datetime.strptime(attendance_date, "%Y-%m-%d") + + # Extract name of the day + attendance_day = attendance_datetime.strftime("%A") + + # Taking all holidays into a list + leaves = [] + holidays = Holidays.objects.all() + for holi in holidays: + start_date = holi.start_date + end_date = holi.end_date + + # Convert start_date and end_date to datetime objects + start_date = datetime.strptime(str(start_date), "%Y-%m-%d") + end_date = datetime.strptime(str(end_date), "%Y-%m-%d") + + # Add dates in between start date and end date including both + current_date = start_date + while current_date <= end_date: + leaves.append(current_date.strftime("%Y-%m-%d")) + current_date += timedelta(days=1) + + # Checking attendance date is in holiday list, if found making the minimum hour to 00:00 + for leave in leaves: + if str(leave) == str(attendance_date): + minimum_hour = "00:00" + break + + # Making a dictonary contains week day value and leave day pairs + company_leaves = {} + company_leave = CompanyLeaves.objects.all() + for com_leave in company_leave: + a = dict(WEEK_DAYS).get(com_leave.based_on_week_day) + b = com_leave.based_on_week + company_leaves[b] = a + + # Checking the attendance date is in which week + week_in_month = str(((attendance_datetime.day - 1) // 7 + 1) - 1) + + # Checking the attendance date is in the company leave or not + for pairs in company_leaves.items(): + # For all weeks based_on_week is None + if str(pairs[0]) == "None": + if str(pairs[1]) == str(attendance_day): + minimum_hour = "00:00" + break + # Checking with based_on_week and attendance_date week + if str(pairs[0]) == week_in_month: + if str(pairs[1]) == str(attendance_day): + minimum_hour = "00:00" + break + return minimum_hour + + +def paginator_qry(qryset, page_number): + """ + This method is used to paginate queryset + """ + paginator = Paginator(qryset, get_pagination()) + qryset = paginator.get_page(page_number) + return qryset + + +def monthly_leave_days(month, year): + leave_dates = [] + holidays = Holidays.objects.filter(start_date__month=month, start_date__year=year) + leave_dates += list(holidays.values_list("start_date", flat=True)) + + company_leaves = CompanyLeaves.objects.all() + for company_leave in company_leaves: + year = year + month = month + based_on_week = company_leave.based_on_week + based_on_week_day = company_leave.based_on_week_day + if based_on_week != None: + calendar.setfirstweekday(6) + month_calendar = calendar.monthcalendar(year, month) + weeks = month_calendar[int(based_on_week)] + weekdays_in_weeks = [day for day in weeks if day != 0] + for day in weekdays_in_weeks: + date_name = datetime.strptime( + f"{year}-{month:02}-{day:02}", "%Y-%m-%d" + ).date() + if ( + date_name.weekday() == int(based_on_week_day) + and date_name not in leave_dates + ): + leave_dates.append(date_name) + else: + calendar.setfirstweekday(0) + month_calendar = calendar.monthcalendar(year, month) + for week in month_calendar: + if week[int(based_on_week_day)] != 0: + date_name = datetime.strptime( + f"{year}-{month:02}-{week[int(based_on_week_day)]:02}", + "%Y-%m-%d", + ).date() + if date_name not in leave_dates: + leave_dates.append(date_name) + return leave_dates + + +def validate_time_in_minutes(value): + """ + this method is used to validate the format of duration like fields. + """ + if len(value) > 5: + raise ValidationError(_("Invalid format, it should be MM:SS format")) + try: + minutes, sec = value.split(":") + if len(minutes) > 2 or len(sec) > 2: + raise ValidationError(_("Invalid time, excepted MM:SS")) + minutes = int(minutes) + sec = int(sec) + if minutes not in range(60) or sec not in range(60): + raise ValidationError(_("Invalid time, excepted MM:SS")) + except ValueError as e: + raise ValidationError(_("Invalid format, excepted MM:SS")) from e diff --git a/attendance/models.py b/attendance/models.py index 94092af7d..8ae21e290 100644 --- a/attendance/models.py +++ b/attendance/models.py @@ -12,129 +12,36 @@ from collections.abc import Iterable from datetime import date, datetime, timedelta import pandas as pd +from django.apps import apps from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q -from django.db.models.signals import post_save +from django.db.models.signals import post_save, pre_delete, pre_save from django.dispatch import receiver +from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from attendance.methods.differentiate import get_diff_dict +from attendance.methods.utils import ( + MONTH_MAPPING, + attendance_date_validate, + format_time, + get_diff_dict, + strtime_seconds, + validate_hh_mm_ss_format, + validate_time_format, + validate_time_in_minutes, +) from base.horilla_company_manager import HorillaCompanyManager -from base.models import Company, EmployeeShift, EmployeeShiftDay, WorkType +from base.methods import is_company_leave, is_holiday +from base.models import Company, EmployeeShift, EmployeeShiftDay, Holidays, WorkType from employee.models import Employee +from horilla.methods import get_horilla_model_class from horilla.models import HorillaModel from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog -from leave.methods import is_company_leave, is_holiday -from leave.models import ( - WEEK_DAYS, - WEEKS, - CompanyLeave, - Holiday, - LeaveRequest, - LeaveType, -) # Create your models here. -def strtime_seconds(time): - """ - this method is used to reconvert time in H:M formate string back to seconds and return it - args: - time : time in H:M format - """ - ftr = [3600, 60, 1] - return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) - - -def format_time(seconds): - """ - This method is used to formate seconds to H:M and return it - args: - seconds : seconds - """ - hour = int(seconds // 3600) - minutes = int((seconds % 3600) // 60) - seconds = int((seconds % 3600) % 60) - return f"{hour:02d}:{minutes:02d}" - - -def validate_hh_mm_ss_format(value): - timeformat = "%H:%M:%S" - try: - validtime = datetime.strptime(value, timeformat) - return validtime.time() # Return the time object if needed - except ValueError as e: - raise ValidationError(_("Invalid format, it should be HH:MM:SS format")) - - -def validate_time_format(value): - """ - this method is used to validate the format of duration like fields. - """ - if len(value) > 6: - raise ValidationError(_("Invalid format, it should be HH:MM format")) - try: - hour, minute = value.split(":") - if len(hour) > 3 or len(minute) > 2: - raise ValidationError(_("Invalid time")) - hour = int(hour) - minute = int(minute) - if len(str(hour)) > 3 or len(str(minute)) > 2 or minute not in range(60): - raise ValidationError(_("Invalid time, excepted MM:SS")) - except ValueError as error: - raise ValidationError(_("Invalid format")) from error - - -def validate_time_in_minutes(value): - """ - this method is used to validate the format of duration like fields. - """ - if len(value) > 5: - raise ValidationError(_("Invalid format, it should be MM:SS format")) - try: - minutes, sec = value.split(":") - if len(minutes) > 2 or len(sec) > 2: - raise ValidationError(_("Invalid time, excepted MM:SS")) - minutes = int(minutes) - sec = int(sec) - if minutes not in range(60) or sec not in range(60): - raise ValidationError(_("Invalid time, excepted MM:SS")) - except ValueError as e: - raise ValidationError(_("Invalid format, excepted MM:SS")) from e - - -def attendance_date_validate(date): - """ - Validates if the provided date is not a future date. - - :param date: The date to validate. - :raises ValidationError: If the provided date is in the future. - """ - today = datetime.today().date() - if not date: - raise ValidationError(_("Check date format.")) - elif date > today: - raise ValidationError(_("You cannot choose a future date.")) - - -month_mapping = { - "january": 1, - "february": 2, - "march": 3, - "april": 4, - "may": 5, - "june": 6, - "july": 7, - "august": 8, - "september": 9, - "october": 10, - "november": 11, - "december": 12, -} - - class AttendanceActivity(HorillaModel): """ AttendanceActivity model @@ -378,7 +285,7 @@ class Attendance(HorillaModel): # Taking all holidays into a list leaves = [] - holidays = Holiday.objects.all() + holidays = Holidays.objects.all() for holi in holidays: start_date = holi.start_date end_date = holi.end_date @@ -511,11 +418,14 @@ class Attendance(HorillaModel): Args: employee_ot (obj): AttendanceOverTime instance """ - approved_leave_requests = self.employee_id.leaverequest_set.filter( - start_date__lte=self.attendance_date, - end_date__gte=self.attendance_date, - status="approved", - ) + if apps.is_installed("leave"): + approved_leave_requests = self.employee_id.leaverequest_set.filter( + start_date__lte=self.attendance_date, + end_date__gte=self.attendance_date, + status="approved", + ) + else: + approved_leave_requests = [] # Create a Q object to combine multiple conditions for the exclude clause exclude_condition = Q() @@ -698,7 +608,7 @@ class AttendanceOverTime(HorillaModel): hrs_to_vlaidate = sum( list( Attendance.objects.filter( - attendance_date__month=month_mapping[self.month], + attendance_date__month=MONTH_MAPPING[self.month], attendance_date__year=self.year, employee_id=self.employee_id, attendance_validated=False, @@ -714,7 +624,7 @@ class AttendanceOverTime(HorillaModel): hrs_to_approve = sum( list( Attendance.objects.filter( - attendance_date__month=month_mapping[self.month], + attendance_date__month=MONTH_MAPPING[self.month], attendance_date__year=self.year, employee_id=self.employee_id, attendance_validated=True, @@ -728,7 +638,7 @@ class AttendanceOverTime(HorillaModel): """ This method will return the index of the month """ - return month_mapping[self.month] + return MONTH_MAPPING[self.month] def save(self, *args, **kwargs): self.hour_account_second = strtime_seconds(self.worked_hours) @@ -787,7 +697,7 @@ class AttendanceLateComeEarlyOut(HorillaModel): """ This method is used to return the total penalties in the late early instance """ - return self.penaltyaccount_set.count() + return self.penaltyaccounts_set.count() def save(self, *args, **kwargs) -> None: super().save(*args, **kwargs) @@ -833,132 +743,6 @@ class AttendanceValidationCondition(HorillaModel): raise ValidationError(_("You cannot add more conditions.")) -months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", -] - - -class PenaltyAccount(HorillaModel): - """ - LateComeEarlyOutPenaltyAccount - """ - - employee_id = models.ForeignKey( - Employee, - on_delete=models.PROTECT, - related_name="penalty_set", - editable=False, - verbose_name="Employee", - null=True, - ) - late_early_id = models.ForeignKey( - AttendanceLateComeEarlyOut, on_delete=models.CASCADE, null=True, editable=False - ) - leave_request_id = models.ForeignKey( - LeaveRequest, null=True, on_delete=models.CASCADE, editable=False - ) - leave_type_id = models.ForeignKey( - LeaveType, - on_delete=models.DO_NOTHING, - blank=True, - null=True, - verbose_name="Leave type", - ) - minus_leaves = models.FloatField(default=0.0, null=True) - deduct_from_carry_forward = models.BooleanField(default=False) - penalty_amount = models.FloatField(default=0.0, null=True) - - def clean(self) -> None: - super().clean() - if not self.leave_type_id and self.minus_leaves: - raise ValidationError( - {"leave_type_id": _("Specify the leave type to deduct the leave.")} - ) - if self.leave_type_id and not self.minus_leaves: - raise ValidationError( - { - "minus_leaves": _( - "If a leave type is chosen for a penalty, minus leaves are required." - ) - } - ) - if not self.minus_leaves and not self.penalty_amount: - raise ValidationError( - { - "leave_type_id": _( - "Either minus leaves or a penalty amount is required" - ) - } - ) - - if ( - self.minus_leaves or self.deduct_from_carry_forward - ) and not self.leave_type_id: - raise ValidationError({"leave_type_id": _("Leave type is required")}) - return - - class Meta: - ordering = ["-created_at"] - - -@receiver(post_save, sender=PenaltyAccount) -def create_initial_stage(sender, instance, created, **kwargs): - """ - This is post save method, used to create initial stage for the recruitment - """ - # only work when creating - if created: - penalty_amount = instance.penalty_amount - if penalty_amount: - from payroll.models.models import Deduction - - penalty = Deduction() - if instance.late_early_id: - penalty.title = f"{instance.late_early_id.get_type_display()} penalty" - penalty.one_time_date = ( - instance.late_early_id.attendance_id.attendance_date - ) - elif instance.leave_request_id: - penalty.title = f"Leave penalty {instance.leave_request_id.end_date}" - penalty.one_time_date = instance.leave_request_id.end_date - else: - penalty.title = f"Penalty on {datetime.today()}" - penalty.one_time_date = datetime.today() - penalty.include_active_employees = False - penalty.is_fixed = True - penalty.amount = instance.penalty_amount - penalty.only_show_under_employee = True - penalty.save() - penalty.include_active_employees = False - penalty.specific_employees.add(instance.employee_id) - penalty.save() - - if instance.leave_type_id and instance.minus_leaves: - available = instance.employee_id.available_leave.filter( - leave_type_id=instance.leave_type_id - ).first() - unit = round(instance.minus_leaves * 2) / 2 - if not instance.deduct_from_carry_forward: - available.available_days = max(0, (available.available_days - unit)) - else: - available.carryforward_days = max( - 0, (available.carryforward_days - unit) - ) - - available.save() - - class GraceTime(HorillaModel): """ Model for saving Grace time @@ -1038,3 +822,278 @@ class AttendanceGeneralSetting(HorillaModel): time_runner = models.BooleanField(default=True) company_id = models.ForeignKey(Company, on_delete=models.CASCADE, null=True) + + +if apps.is_installed("leave") and apps.is_installed("payroll"): + + class PenaltyAccount(HorillaModel): + """ + LateComeEarlyOutPenaltyAccount + """ + + employee_id = models.ForeignKey( + Employee, + on_delete=models.PROTECT, + related_name="penalty_set", + editable=False, + verbose_name="Employee", + null=True, + ) + late_early_id = models.ForeignKey( + AttendanceLateComeEarlyOut, + on_delete=models.CASCADE, + null=True, + editable=False, + ) + leave_request_id = models.ForeignKey( + "leave.LeaveRequest", null=True, on_delete=models.CASCADE, editable=False + ) + leave_type_id = models.ForeignKey( + "leave.LeaveType", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + verbose_name="Leave type", + ) + minus_leaves = models.FloatField(default=0.0, null=True) + deduct_from_carry_forward = models.BooleanField(default=False) + penalty_amount = models.FloatField(default=0.0, null=True) + + def clean(self) -> None: + super().clean() + if not self.leave_type_id and self.minus_leaves: + raise ValidationError( + {"leave_type_id": _("Specify the leave type to deduct the leave.")} + ) + if self.leave_type_id and not self.minus_leaves: + raise ValidationError( + { + "minus_leaves": _( + "If a leave type is chosen for a penalty, minus leaves are required." + ) + } + ) + if not self.minus_leaves and not self.penalty_amount: + raise ValidationError( + { + "leave_type_id": _( + "Either minus leaves or a penalty amount is required" + ) + } + ) + + if ( + self.minus_leaves or self.deduct_from_carry_forward + ) and not self.leave_type_id: + raise ValidationError({"leave_type_id": _("Leave type is required")}) + return + + class Meta: + ordering = ["-created_at"] + + @receiver(post_save, sender=PenaltyAccount) + def create_initial_stage(sender, instance, created, **kwargs): + """ + This is post save method, used to create initial stage for the recruitment + """ + # only work when creating + if created: + penalty_amount = instance.penalty_amount + if penalty_amount: + Deduction = get_horilla_model_class( + app_label="payroll", model="deduction" + ) + penalty = Deduction() + if instance.late_early_id: + penalty.title = ( + f"{instance.late_early_id.get_type_display()} penalty" + ) + penalty.one_time_date = ( + instance.late_early_id.attendance_id.attendance_date + ) + elif instance.leave_request_id: + penalty.title = ( + f"Leave penalty {instance.leave_request_id.end_date}" + ) + penalty.one_time_date = instance.leave_request_id.end_date + else: + penalty.title = f"Penalty on {datetime.today()}" + penalty.one_time_date = datetime.today() + penalty.include_active_employees = False + penalty.is_fixed = True + penalty.amount = instance.penalty_amount + penalty.only_show_under_employee = True + penalty.save() + penalty.include_active_employees = False + penalty.specific_employees.add(instance.employee_id) + penalty.save() + + if instance.leave_type_id and instance.minus_leaves: + available = instance.employee_id.available_leave.filter( + leave_type_id=instance.leave_type_id + ).first() + unit = round(instance.minus_leaves * 2) / 2 + if not instance.deduct_from_carry_forward: + available.available_days = max(0, (available.available_days - unit)) + else: + available.carryforward_days = max( + 0, (available.carryforward_days - unit) + ) + + available.save() + + +class WorkRecords(models.Model): + """ + WorkRecord Model + """ + + choices = [ + ("FDP", _("Present")), + ("HDP", _("Half Day Present")), + ("ABS", _("Absent")), + ("HD", _("Holiday/Company Leave")), + ("CONF", _("Conflict")), + ("DFT", _("Draft")), + ] + + record_name = models.CharField(max_length=250, null=True, blank=True) + work_record_type = models.CharField(max_length=5, null=True, choices=choices) + employee_id = models.ForeignKey( + Employee, on_delete=models.PROTECT, verbose_name=_("Employee") + ) + date = models.DateField(null=True, blank=True) + at_work = models.CharField( + null=True, + blank=True, + validators=[ + validate_time_format, + ], + default="00:00", + max_length=5, + ) + min_hour = models.CharField( + null=True, + blank=True, + validators=[ + validate_time_format, + ], + default="00:00", + max_length=5, + ) + at_work_second = models.IntegerField(null=True, blank=True, default=0) + min_hour_second = models.IntegerField(null=True, blank=True, default=0) + note = models.TextField(max_length=255) + message = models.CharField(max_length=30, null=True, blank=True) + is_attendance_record = models.BooleanField(default=False) + is_leave_record = models.BooleanField(default=False) + day_percentage = models.FloatField(default=0) + last_update = models.DateTimeField(null=True, blank=True) + objects = HorillaCompanyManager("employee_id__employee_work_info__company_id") + + def save(self, *args, **kwargs): + self.last_update = timezone.now() + + super().save(*args, **kwargs) + + def clean(self): + super().clean() + if not 0.0 <= self.day_percentage <= 1.0: + raise ValidationError(_("Day percentage must be between 0.0 and 1.0")) + + def __str__(self): + return ( + self.record_name + if self.record_name is not None + else f"{self.work_record_type}-{self.date}" + ) + + +class OverrideAttendances(Attendance): + """ + Class to override Attendance model save method + """ + + # Additional fields and methods specific to AnotherModel + @receiver(post_save, sender=Attendance) + def attendance_post_save(sender, instance, **kwargs): + """ + Overriding Attendance model save method + """ + if instance.first_save: + min_hour_second = strtime_seconds(instance.minimum_hour) + at_work_second = strtime_seconds(instance.attendance_worked_hour) + + status = "FDP" if instance.at_work_second >= min_hour_second else "HDP" + + status = "CONF" if instance.attendance_validated is False else status + message = ( + _("Validate the attendance") if status == "CONF" else _("Validated") + ) + + message = ( + _("Incomplete minimum hour") + if status == "HDP" and min_hour_second > at_work_second + else message + ) + work_record = WorkRecords.objects.filter( + date=instance.attendance_date, + is_attendance_record=True, + employee_id=instance.employee_id, + ) + work_record = ( + WorkRecords() + if not WorkRecords.objects.filter( + date=instance.attendance_date, + employee_id=instance.employee_id, + ).exists() + else WorkRecords.objects.filter( + date=instance.attendance_date, + employee_id=instance.employee_id, + ).first() + ) + work_record.employee_id = instance.employee_id + work_record.date = instance.attendance_date + work_record.at_work = instance.attendance_worked_hour + work_record.min_hour = instance.minimum_hour + work_record.min_hour_second = min_hour_second + work_record.at_work_second = at_work_second + work_record.work_record_type = status + work_record.message = message + work_record.is_attendance_record = True + if instance.attendance_validated: + work_record.day_percentage = ( + 1.00 if at_work_second > min_hour_second / 2 else 0.50 + ) + work_record.save() + + if status == "HDP" and work_record.is_leave_record: + message = _("Half day leave") + + if status == "FDP": + message = _("Present") + + work_record.message = message + work_record.save() + + message = work_record.message + status = work_record.work_record_type + if not instance.attendance_clock_out: + status = "FDP" + message = _("Currently working") + work_record.message = message + work_record.work_record_type = status + work_record.save() + + @receiver(pre_delete, sender=Attendance) + def attendance_pre_delete(sender, instance, **_kwargs): + """ + Overriding Attendance model delete method + """ + # Perform any actions before deleting the instance + # ... + WorkRecords.objects.filter( + employee_id=instance.employee_id, + is_attendance_record=True, + date=instance.attendance_date, + ).delete() diff --git a/attendance/sidebar.py b/attendance/sidebar.py index 4c4f40d8e..e6e9d6787 100644 --- a/attendance/sidebar.py +++ b/attendance/sidebar.py @@ -4,6 +4,7 @@ attendance/sidebar.py from datetime import datetime +from django.apps import apps from django.urls import reverse from django.utils.translation import gettext_lazy as trans @@ -53,6 +54,14 @@ SUBMENUS = [ "redirect": reverse("view-my-attendance"), }, ] +if apps.is_installed("leave"): + SUBMENUS.append( + { + "menu": trans("Work Records"), + "redirect": reverse("work-records"), + "accessibility": "attendance.sidebar.work_record_accessibility", + }, + ) def attendances_accessibility(request, submenu, user_perms, *args, **kwargs): diff --git a/attendance/static/attendance/actions.js b/attendance/static/attendance/actions.js index c8eefa3a3..520d700f3 100644 --- a/attendance/static/attendance/actions.js +++ b/attendance/static/attendance/actions.js @@ -959,7 +959,7 @@ $("#validateAttendances").click(function (e) { }); } else { Swal.fire({ - text: "confirmMessage", + text: confirmMessage, icon: "info", showCancelButton: true, confirmButtonColor: "#008000", diff --git a/attendance/static/dashboard/attendanceChart.js b/attendance/static/dashboard/attendanceChart.js index eb95f1499..a521f3bc6 100644 --- a/attendance/static/dashboard/attendanceChart.js +++ b/attendance/static/dashboard/attendanceChart.js @@ -1,12 +1,14 @@ +staticUrl = $("#statiUrl").attr("data-url"); $(document).ready(function () { - - // initializing the department overtime chart. + // initializing the department overtime chart. var departmentChartData = { labels: [], datasets: [], }; window["departmentOvertimeChart"] = {}; - const departmentOvertimeChart = document.getElementById("departmentOverChart"); + const departmentOvertimeChart = document.getElementById( + "departmentOverChart" + ); if (departmentOvertimeChart) { var departmentAttendanceChart = new Chart(departmentOvertimeChart, { type: "pie", @@ -17,7 +19,8 @@ $(document).ready(function () { }, plugins: [ { - afterRender: (departmentAttendanceChart) => emptyOvertimeChart(departmentAttendanceChart), + afterRender: (departmentAttendanceChart) => + emptyOvertimeChart(departmentAttendanceChart), }, ], }); @@ -43,14 +46,14 @@ $(document).ready(function () { }, }); - // Function to update the department overtime chart according to the response fetched from backend. + // Function to update the department overtime chart according to the response fetched from backend. function departmentDataUpdate(response) { departmentChartData.labels = response.labels; - departmentChartData.datasets=response.dataset; - departmentChartData.message=response.message; - departmentChartData.emptyImageSrc=response.emptyImageSrc; - if (departmentAttendanceChart){ + departmentChartData.datasets = response.dataset; + departmentChartData.message = response.message; + departmentChartData.emptyImageSrc = response.emptyImageSrc; + if (departmentAttendanceChart) { departmentAttendanceChart.update(); } } @@ -77,7 +80,7 @@ $(document).ready(function () { }); } - // Function to update the input fields according to type select field. + // Function to update the input fields according to type select field. function changeDepartmentView(element) { var dataType = $(element).val(); @@ -93,10 +96,9 @@ $(document).ready(function () { $("#department_month2").remove(); if (dataType === "weekly") { $("#department_month").prop("type", "week"); - if (currentWeek <10){ + if (currentWeek < 10) { $("#department_month").val(`${year}-W0${currentWeek}`); - } - else { + } else { $("#department_month").val(`${year}-W${currentWeek}`); } changeDepartmentMonth(); @@ -112,12 +114,13 @@ $(document).ready(function () { } } - // Function for empty message for department overtime chart. + // Function for empty message for department overtime chart. function emptyOvertimeChart(departmentAttendanceChart, args, options) { flag = false; for (let i = 0; i < departmentAttendanceChart.data.datasets.length; i++) { - flag = flag + departmentAttendanceChart.data.datasets[i].data.some(Boolean); + flag = + flag + departmentAttendanceChart.data.datasets[i].data.some(Boolean); } if (!flag) { const { ctx, canvas } = departmentAttendanceChart; @@ -133,7 +136,7 @@ $(document).ready(function () { var noDataImage = new Image(); noDataImage.src = departmentAttendanceChart.data.emptyImageSrc ? departmentAttendanceChart.data.emptyImageSrc - : "/static/images/ui/joiningchart.png"; + : staticUrl + "images/ui/joiningchart.png"; message = departmentAttendanceChart.data.message ? departmentAttendanceChart.data.message @@ -153,7 +156,7 @@ $(document).ready(function () { } } - // Ajax request to create department overtime chart initially. + // Ajax request to create department overtime chart initially. $.ajax({ url: "/attendance/department-overtime-chart", @@ -163,29 +166,26 @@ $(document).ready(function () { "X-Requested-With": "XMLHttpRequest", }, success: (response) => { - departmentDataUpdate(response); - }, error: (error) => { console.log("Error", error); }, }); - // Functions to update department overtime chart while changing the date input field and select input field. + // Functions to update department overtime chart while changing the date input field and select input field. - $("#departmentChartCard").on("change","#department_date_type", function (e) { - changeDepartmentView($(this)) - }) + $("#departmentChartCard").on("change", "#department_date_type", function (e) { + changeDepartmentView($(this)); + }); - $("#departmentChartCard").on("change","#department_month", function (e) { - changeDepartmentMonth() - }) - - $("#departmentChartCard").on("change","#department_month2", function (e) { - changeDepartmentMonth() - }) + $("#departmentChartCard").on("change", "#department_month", function (e) { + changeDepartmentMonth(); + }); + $("#departmentChartCard").on("change", "#department_month2", function (e) { + changeDepartmentMonth(); + }); }); var data; @@ -222,7 +222,7 @@ function createAttendanceChart(dataSet, labels) { }; // Create chart using the Chart.js library window["attendanceChart"] = {}; - if (document.getElementById("dailyAnalytic")){ + if (document.getElementById("dailyAnalytic")) { const ctx = document.getElementById("dailyAnalytic").getContext("2d"); attendanceChart = new Chart(ctx, { type: "bar", @@ -278,7 +278,8 @@ function createAttendanceChart(dataSet, labels) { error: (error) => {}, }); } else { - window.location.href = "/attendance/late-come-early-out-view" + parms; + window.location.href = + "/attendance/late-come-early-out-view" + parms; } }, }, @@ -326,10 +327,9 @@ function changeView(element) { $("#attendance_month2").remove(); if (dataType === "weekly") { $("#attendance_month").prop("type", "week"); - if (currentWeek <10){ + if (currentWeek < 10) { $("#attendance_month").val(`${year}-W0${currentWeek}`); - } - else { + } else { $("#attendance_month").val(`${year}-W${currentWeek}`); } changeMonth(); @@ -344,11 +344,8 @@ function changeView(element) { } } } -if (document.getElementById("pendingHoursCanvas")){ - var chart = new Chart( - document.getElementById("pendingHoursCanvas"), - {} - ); +if (document.getElementById("pendingHoursCanvas")) { + var chart = new Chart(document.getElementById("pendingHoursCanvas"), {}); } window["pendingHoursCanvas"] = chart; function pendingHourChart(year, month) { @@ -358,7 +355,7 @@ function pendingHourChart(year, month) { data: { month: month, year: year }, success: function (response) { var ctx = document.getElementById("pendingHoursCanvas"); - if (ctx){ + if (ctx) { pendingHoursCanvas.destroy(); pendingHoursCanvas = new Chart(ctx, { type: "bar", // Bar chart type diff --git a/attendance/templates/attendance/dashboard/dashboard.html b/attendance/templates/attendance/dashboard/dashboard.html index df8b32e11..8112d8579 100644 --- a/attendance/templates/attendance/dashboard/dashboard.html +++ b/attendance/templates/attendance/dashboard/dashboard.html @@ -210,7 +210,7 @@ {% else %}
- +

{% trans "No employees on break...." %}

diff --git a/attendance/templates/attendance/dashboard/overtime_table.html b/attendance/templates/attendance/dashboard/overtime_table.html index 3e79f8789..bc164d32e 100644 --- a/attendance/templates/attendance/dashboard/overtime_table.html +++ b/attendance/templates/attendance/dashboard/overtime_table.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if overtime_attendances %}
@@ -85,7 +85,7 @@ {% else %}
- +

{% trans "No Overtime to Validate...." %}

diff --git a/attendance/templates/attendance/dashboard/to_validate_table.html b/attendance/templates/attendance/dashboard/to_validate_table.html index 18fc51933..3caaf7a0b 100644 --- a/attendance/templates/attendance/dashboard/to_validate_table.html +++ b/attendance/templates/attendance/dashboard/to_validate_table.html @@ -133,7 +133,7 @@ {% else %}
- +

{% trans "All Attendance Validated." %}

diff --git a/attendance/templates/attendance/grace_time/grace_time_table.html b/attendance/templates/attendance/grace_time/grace_time_table.html index 119c053f6..5d340920b 100644 --- a/attendance/templates/attendance/grace_time/grace_time_table.html +++ b/attendance/templates/attendance/grace_time/grace_time_table.html @@ -68,7 +68,7 @@ {% endif %} {% if perms.base.delete_gracetime %}
{% csrf_token %}
{% trans "At Work" %}
{% trans "Penalties" %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
{% trans "Actions" %}
{% endif %}
@@ -101,10 +101,10 @@
Penalties :{{late_in_early_out.get_penalties_count}}
{% endif %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts %}
{% trans "At Work" %}
{% trans "Penalties" %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
{% trans "Actions" %}
{% endif %} {% comment %}
{% endcomment %} @@ -90,10 +90,10 @@
Penalties :{{late_in_early_out.get_penalties_count}}
{% endif %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts %} - {% for message in messages %} -
{{ message }}
- {% endfor %} -
- - {% if late_in_early_out_ids %} - - {% endif %} -{% endif %} -
- -
-
-
-
-
- Mary Magdalene -
-
- {{ instance.employee_id.get_full_name }} - - {{ instance.employee_id.get_department }} / {{ instance.employee_id.get_job_position }} - -
-
-
- - {{ form.as_p }} +{% load i18n %} {% load horillafilters %} -
-
-
-
{% trans "Leave Type" %}
-
{% trans "Available Days" %}
-
- {% trans "Carry Forward Days" %} -
-
-
-
- {% for acc in available %} -
-
{{ acc.leave_type_id }}
-
{{ acc.available_days }}
-
{{ acc.carryforward_days }}
-
+{% if messages %} +
+ {% for message in messages %} +
{{ message }}
{% endfor %} -
-
    -
  1. - {% trans "Leave type is optional when 'minus leave' is 0" %} -
  2. -
  3. - {% trans "Penalty amount will affect payslip on the date" %} -
  4. -
  5. - {% trans "By default minus leave will cut/deduct from available leaves" %} + setTimeout(() => { + $(".oh-modal__close--custom").click(); + const spanElement = document.querySelector(".oh-span__class"); + if (spanElement) { + spanElement.click(); + } + }, 1000); + + {% if late_in_early_out_ids %} + + {% endif %} +{% endif %} + +
    +
  6. -
  7. - {% trans "By enabling 'Deduct from carry forward' leave will cut/deduct from carry forward days" %} -
  8. -
-
- -
- + + +
+ +
+
+
+
+ Mary Magdalene +
+
+ {{ instance.employee_id.get_full_name }} + + {{ instance.employee_id.get_department }} / {{ instance.employee_id.get_job_position }} + +
+
+
+ +
+ {{ form.as_p }} + {% if "leave"|app_installed %} +
+
+
+
{% trans "Leave Type" %}
+
{% trans "Available Days" %}
+
+ {% trans "Carry Forward Days" %} +
+
+
+
+ {% for acc in available %} +
+
{{ acc.leave_type_id }}
+
{{ acc.available_days }}
+
{{ acc.carryforward_days }}
+
+ {% endfor %} +
+
+
    +
  1. + {% trans "Leave type is optional when 'minus leave' is 0" %} +
  2. +
  3. + {% trans "Penalty amount will affect payslip on the date" %} +
  4. +
  5. + {% trans "By default minus leave will cut/deduct from available leaves" %} +
  6. +
  7. + {% trans "By enabling 'Deduct from carry forward' leave will cut/deduct from carry forward days" + %} +
  8. +
+ {% endif %} +
+ +
+
+
diff --git a/attendance/templates/attendance/work_record/work_record_create.html b/attendance/templates/attendance/work_record/work_record_create.html new file mode 100644 index 000000000..020c67c3f --- /dev/null +++ b/attendance/templates/attendance/work_record/work_record_create.html @@ -0,0 +1,16 @@ +{% extends 'index.html' %} +{% load i18n %} + +{% block content %} +
+
+ {% csrf_token %} + {{contract_form.as_p}} + +
+ + +
+ + +{% endblock content %} diff --git a/attendance/templates/attendance/work_record/work_record_employees_view.html b/attendance/templates/attendance/work_record/work_record_employees_view.html new file mode 100644 index 000000000..4f5ec3e85 --- /dev/null +++ b/attendance/templates/attendance/work_record/work_record_employees_view.html @@ -0,0 +1,74 @@ +{% extends 'index.html' %} +{% load current_month_record %} +{% block content %} + + + +
+
+
+

{% trans "Date:" %} {{ current_date }}

+

{% trans "Month:" %} {{ current_date|date:"F" }}

+
+ + + + + {% for day in current_month_dates_list %} + + {% endfor %} + + + + + {% for employee in employees %} + + + {% for date in current_month_dates_list %} + + {% endfor %} + + {% endfor %} + +
{% trans "Work Records" %}{{ day.day }}
{{ employee }} + {% for work_record in employee.workrecord_set.all|current_month_record %} + {% if work_record.start_datetime.date == date %} + {% if work_record.work_record_type.is_timeoff %} +
{% trans "A" %}
+ {% else %} +
{% trans "P" %}
+ {% endif %} + {% endif %} + {% endfor %} +
+
+
+ +{% endblock content %} diff --git a/attendance/templates/attendance/work_record/work_record_view copy.html b/attendance/templates/attendance/work_record/work_record_view copy.html new file mode 100644 index 000000000..a95ab3b2b --- /dev/null +++ b/attendance/templates/attendance/work_record/work_record_view copy.html @@ -0,0 +1,37 @@ +{% extends 'index.html' %} + +{% block content %} + + + +
+
+
+
{% trans "record_type_name" %}
+ +
+
+
+ {% for work_record in work_records %} +
+
+
+
+ Mary Magdalene +
+ {{work_record.work_record_name}} +
+
+ +
+ {% endfor %} +
+
+ + +{% endblock content %} diff --git a/attendance/templates/requests/attendance/attendance_comment.html b/attendance/templates/requests/attendance/attendance_comment.html index c2dde32a0..4c251d858 100644 --- a/attendance/templates/requests/attendance/attendance_comment.html +++ b/attendance/templates/requests/attendance/attendance_comment.html @@ -136,7 +136,7 @@ >
diff --git a/attendance/templates/requests/attendance/comment_view.html b/attendance/templates/requests/attendance/comment_view.html index 2e627b3bd..09ea01885 100644 --- a/attendance/templates/requests/attendance/comment_view.html +++ b/attendance/templates/requests/attendance/comment_view.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} +{% load i18n static %} {% if messages %}
{% for message in messages %} @@ -13,7 +13,7 @@
{% trans "There is no comments to show." %} - +
diff --git a/attendance/urls.py b/attendance/urls.py index 0fb68ad03..4b43e7ad8 100644 --- a/attendance/urls.py +++ b/attendance/urls.py @@ -5,6 +5,7 @@ This page is used to map request or url path with function """ +from django.apps import apps from django.urls import path import attendance.views.clock_in_out @@ -12,10 +13,19 @@ import attendance.views.dashboard import attendance.views.penalty import attendance.views.requests import attendance.views.search +import base +from base.forms import AttendanceAllowedIPForm +from base.models import AttendanceAllowedIP from .views import views urlpatterns = [ + path( + "profile-attendance-tab", + views.profile_attendance_tab, + name="profile-attendance-tab", + ), + path("attendance-tab/", views.attendance_tab, name="attendance-tab"), path("attendance-create", views.attendance_create, name="attendance-create"), path("attendance-excel", views.attendance_excel, name="attendance-excel"), path( @@ -334,14 +344,6 @@ urlpatterns = [ path( "pending-hours/", attendance.views.dashboard.pending_hours, name="pending-hours" ), - path( - "cut-penalty//", - attendance.views.penalty.cut_available_leave, - name="cut-penalty", - ), - path( - "view-penalties", attendance.views.penalty.view_penalties, name="view-penalties" - ), path("create-garce-time", views.create_grace_time, name="create-grace-time"), path( "update-garce-time//", @@ -391,4 +393,86 @@ urlpatterns = [ attendance.views.requests.get_employee_shift, name="get-employee-shift", ), + path( + "cut-penalty//", + attendance.views.penalty.cut_available_leave, + name="cut-penalty", + ), + path( + "dashboard-overtime-approve", + attendance.views.dashboard.dashboard_overtime_approve, + name="dashboard-overtime-approve", + ), + path( + "dashboard-attendance-validate", + attendance.views.dashboard.dashboard_attendance_validate, + name="dashboard-attendance-validate", + ), + path( + "attendance-settings-view/", + views.validation_condition_view, + name="attendance-settings-view", + ), + path( + "track-late-come-early-out", + views.track_late_come_early_out, + name="track-late-come-early-out", + ), + path( + "enable-disable-tracking-late-come-early-out", + views.enable_disable_tracking_late_come_early_out, + name="enable-disable-tracking-late-come-early-out", + ), + path( + "grace-settings-view/", + views.grace_time_view, + name="grace-settings-view", + ), + path( + "settings/attendance-settings-create/", + views.validation_condition_create, + name="attendance-settings-create", + ), + path( + "settings/attendance-settings-update//", + views.validation_condition_update, + name="attendance-settings-update", + ), + path( + "allowed-ips/", + views.allowed_ips, + name="allowed-ips", + ), + path( + "settings/enable-ip-restriction/", + views.enable_ip_restriction, + name="enable-ip-restriction", + ), + path( + "settings/create-allowed-ip/", + views.create_allowed_ips, + name="create-allowed-ip", + ), + path( + "settings/delete-allowed-ip/", + views.delete_allowed_ips, + name="delete-allowed-ip", + ), + path( + "settings/edit-allowed-ip/", + views.edit_allowed_ips, + name="edit-allowed-ip", + ), + path( + "settings/add-remove-ip-fields/", + base.views.add_remove_dynamic_fields, + name="add-remove-ip-fields", + kwargs={ + "model": AttendanceAllowedIP, + "form_class": AttendanceAllowedIPForm, + "template": "attendance/ip_restriction/add_more_ip_fields.html", + "field_type": "character", + "field_name_pre": "ip_address", + }, + ), ] diff --git a/attendance/views.py b/attendance/views.py index 016ba9f96..4851bf821 100644 --- a/attendance/views.py +++ b/attendance/views.py @@ -40,6 +40,15 @@ from attendance.forms import ( AttendanceUpdateForm, AttendanceValidationConditionForm, ) +from attendance.methods.utils import ( + activity_datetime, + employee_exists, + format_time, + is_reportingmanger, + overtime_calculation, + shift_schedule_today, + strtime_seconds, +) from attendance.models import ( Attendance, AttendanceActivity, @@ -61,56 +70,6 @@ from notifications.signals import notify # Create your views here. -def intersection_list(list1, list2): - """ - This method is used to intersect two list - """ - return [value for value in list1 if value in list2] - - -def format_time(seconds): - """ - this method is used to formate seconds to H:M and return it - args: - seconds : seconds - """ - - hour = int(seconds // 3600) - minutes = int((seconds % 3600) // 60) - seconds = int((seconds % 3600) % 60) - return f"{hour:02d}:{minutes:02d}" - - -def strtime_seconds(time): - """ - this method is used reconvert time in H:M formate string back to seconds and return it - args: - time : time in H:M format - """ - - ftr = [3600, 60, 1] - return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) - - -def is_reportingmanger(request, instance): - """ - if the instance have employee id field then you can use this method to know the - request user employee is the reporting manager of the instance - args : - request : request - instance : an object or instance of any model contain employee_id foreign key field - """ - - manager = request.user.employee_get - try: - employee_workinfo_manager = ( - instance.employee_id.employee_work_info.reporting_manager_id - ) - except Exception: - return HttpResponse("This Employee Dont Have any work information") - return manager == employee_workinfo_manager - - def late_come_create(attendance): """ used to create late come report @@ -722,54 +681,6 @@ def attendance_activity_delete(request, obj_id): return redirect("/attendance/attendance-activity-view") -def employee_exists(request): - """ - This method return the employee instance and work info if not exists return None instead - """ - employee, employee_work_info = None, None - try: - employee = request.user.employee_get - employee_work_info = employee.employee_work_info - finally: - return (employee, employee_work_info) - - -def shift_schedule_today(day, shift): - """ - This function is used to find shift schedules for the day, - it will returns min hour,start time seconds end time seconds - args: - shift : shift instance - day : shift day object - """ - schedule_today = day.day_schedule.filter(shift_id=shift) - start_time_sec, end_time_sec, minimum_hour = 0, 0, "00:00" - if schedule_today.exists(): - schedule_today = schedule_today[0] - minimum_hour = schedule_today.minimum_working_hour - start_time_sec = strtime_seconds(schedule_today.start_time.strftime("%H:%M")) - end_time_sec = strtime_seconds(schedule_today.end_time.strftime("%H:%M")) - return (minimum_hour, start_time_sec, end_time_sec) - - -def overtime_calculation(attendance): - """ - This method is used to calculate overtime of the attendance, it will - return difference between attendance worked hour and minimum hour if - and only worked hour greater than minimum hour, else return 00:00 - args: - attendance : attendance instance - """ - - minimum_hour = attendance.minimum_hour - at_work = attendance.attendance_worked_hour - at_work_sec = strtime_seconds(at_work) - minimum_hour_sec = strtime_seconds(minimum_hour) - if at_work_sec > minimum_hour_sec: - return format_time((at_work_sec - minimum_hour_sec)) - return "00:00" - - def clock_in_attendance_and_activity( employee, date_today, @@ -898,30 +809,6 @@ def clock_in(request): ) -def activity_datetime(attendance_activity): - """ - This method is used to convert clock-in and clock-out of activity as datetime object - args: - attendance_activity : attendance activity instance - """ - - # in - in_year = attendance_activity.clock_in_date.year - in_month = attendance_activity.clock_in_date.month - in_day = attendance_activity.clock_in_date.day - in_hour = attendance_activity.clock_in.hour - in_minute = attendance_activity.clock_in.minute - # out - out_year = attendance_activity.clock_out_date.year - out_month = attendance_activity.clock_out_date.month - out_day = attendance_activity.clock_out_date.day - out_hour = attendance_activity.clock_out.hour - out_minute = attendance_activity.clock_out.minute - return datetime(in_year, in_month, in_day, in_hour, in_minute), datetime( - out_year, out_month, out_day, out_hour, out_minute - ) - - def clock_out_attendance_and_activity(employee, date_today, now): """ Clock out the attendance and activity diff --git a/attendance/views/clock_in_out.py b/attendance/views/clock_in_out.py index 4ffdf1a9c..2e9f7efea 100644 --- a/attendance/views/clock_in_out.py +++ b/attendance/views/clock_in_out.py @@ -10,21 +10,21 @@ from django.db.models import Q from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ -from attendance.models import ( - Attendance, - AttendanceActivity, - AttendanceLateComeEarlyOut, - GraceTime, -) -from attendance.views.views import ( +from attendance.methods.utils import ( activity_datetime, - attendance_validate, employee_exists, format_time, overtime_calculation, shift_schedule_today, strtime_seconds, ) +from attendance.models import ( + Attendance, + AttendanceActivity, + AttendanceLateComeEarlyOut, + GraceTime, +) +from attendance.views.views import attendance_validate from base.context_processors import ( enable_late_come_early_out_tracking, timerunner_enabled, diff --git a/attendance/views/dashboard.py b/attendance/views/dashboard.py index 7cb7fe1a4..88e2e7f8e 100644 --- a/attendance/views/dashboard.py +++ b/attendance/views/dashboard.py @@ -4,11 +4,11 @@ dashboard.py This module is used to register endpoints for dashboard-related requests """ -import calendar import json -from datetime import date, datetime, timedelta +from datetime import date, datetime -from django.db.models import Q, Sum +from django.apps import apps +from django.db.models import Q from django.http import JsonResponse from django.shortcuts import render from django.utils.translation import gettext_lazy as _ @@ -18,19 +18,25 @@ from attendance.filters import ( AttendanceOverTimeFilter, LateComeEarlyOutFilter, ) +from attendance.methods.utils import ( + get_month_start_end_dates, + get_week_start_end_dates, + pending_hour_data, + worked_hour_data, +) from attendance.models import ( Attendance, AttendanceLateComeEarlyOut, - AttendanceOverTime, AttendanceValidationCondition, ) from attendance.views.views import strtime_seconds from base.methods import filtersubordinates -from base.models import Department, EmployeeShiftSchedule +from base.models import Department from employee.models import Employee from employee.not_in_out_dashboard import paginator_qry +from horilla import settings from horilla.decorators import hx_request_required, login_required -from leave.models import LeaveRequest +from horilla.methods import get_horilla_model_class def find_on_time(request, today, week_day, department=None): @@ -56,7 +62,11 @@ def find_expected_attendances(week_day): This method is used to find count of expected attendances for the week day """ employees = Employee.objects.filter(is_active=True) - on_leave = LeaveRequest.objects.filter(status="Approved") + if apps.is_installed("leave"): + LeaveRequest = get_horilla_model_class(app_label="leave", model="leaverequest") + on_leave = LeaveRequest.objects.filter(status="Approved") + else: + on_leave = [] expected_attendances = len(employees) - len(on_leave) return expected_attendances @@ -232,38 +242,6 @@ def find_early_out(start_date, end_date=None, department=None): return early_out_obj -def get_week_start_end_dates(week): - """ - This method is use to return the start and end date of the week - """ - # Parse the ISO week date - year, week_number = map(int, week.split("-W")) - - # Get the date of the first day of the week - start_date = datetime.strptime(f"{year}-W{week_number}-1", "%Y-W%W-%w").date() - - # Calculate the end date by adding 6 days to the start date - end_date = start_date + timedelta(days=6) - - return start_date, end_date - - -def get_month_start_end_dates(year_month): - """ - This method is use to return the start and end date of the month - """ - # split year and month separately - year, month = map(int, year_month.split("-")) - # Get the first day of the month - start_date = datetime(year, month, 1).date() - - # Get the last day of the month - _, last_day = calendar.monthrange(year, month) - end_date = datetime(year, month, last_day).date() - - return start_date, end_date - - def generate_data_set(request, start_date, type, end_date, dept): """ This method is used to generate all the dashboard data @@ -347,42 +325,6 @@ def dashboard_attendance(request): return JsonResponse({"dataSet": data_set, "labels": labels, "message": message}) -def worked_hour_data(labels, records): - """ - To find all the worked hours - """ - data = { - "label": "Worked Hours", - "backgroundColor": "rgba(75, 192, 192, 0.6)", - } - dept_records = [] - for dept in labels: - total_sum = records.filter( - employee_id__employee_work_info__department_id__department=dept - ).aggregate(total_sum=Sum("hour_account_second"))["total_sum"] - dept_records.append(total_sum / 3600 if total_sum else 0) - data["data"] = dept_records - return data - - -def pending_hour_data(labels, records): - """ - To find all the pending hours - """ - data = { - "label": "Pending Hours", - "backgroundColor": "rgba(255, 99, 132, 0.6)", - } - dept_records = [] - for dept in labels: - total_sum = records.filter( - employee_id__employee_work_info__department_id__department=dept - ).aggregate(total_sum=Sum("hour_pending_second"))["total_sum"] - dept_records.append(total_sum / 3600 if total_sum else 0) - data["data"] = dept_records - return data - - def pending_hours(request): """ pending hours chart dashboard view @@ -472,7 +414,67 @@ def department_overtime_chart(request): "labels": departments, "department_total": department_total, "message": _("No validated Overtimes were found"), - "emptyImageSrc": "/static/images/ui/overtime-icon.png", + "emptyImageSrc": f"/{settings.STATIC_URL}images/ui/overtime-icon.png", } return JsonResponse(response) + + +@login_required +def dashboard_overtime_approve(request): + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + min_ot = strtime_seconds("00:00") + if apps.is_installed("attendance"): + from attendance.models import Attendance, AttendanceValidationCondition + + condition = AttendanceValidationCondition.objects.first() + if condition is not None and condition.minimum_overtime_to_approve is not None: + min_ot = strtime_seconds(condition.minimum_overtime_to_approve) + ot_attendances = Attendance.objects.filter( + overtime_second__gte=min_ot, + attendance_validated=True, + employee_id__is_active=True, + attendance_overtime_approve=False, + ) + else: + ot_attendances = None + ot_attendances = filtersubordinates( + request, ot_attendances, "attendance.change_attendance" + ) + ot_attendances = paginator_qry(ot_attendances, page_number) + ot_attendances_ids = json.dumps([instance.id for instance in ot_attendances]) + return render( + request, + "request_and_approve/overtime_approve.html", + { + "overtime_attendances": ot_attendances, + "ot_attendances_ids": ot_attendances_ids, + "pd": previous_data, + }, + ) + + +@login_required +def dashboard_attendance_validate(request): + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + validate_attendances = Attendance.objects.filter( + attendance_validated=False, employee_id__is_active=True + ) + validate_attendances = filtersubordinates( + request, validate_attendances, "attendance.change_attendance" + ) + validate_attendances = paginator_qry(validate_attendances, page_number) + validate_attendances_ids = json.dumps( + [instance.id for instance in validate_attendances] + ) + return render( + request, + "request_and_approve/attendance_validate.html", + { + "validate_attendances": validate_attendances, + "validate_attendances_ids": validate_attendances_ids, + "pd": previous_data, + }, + ) diff --git a/attendance/views/penalty.py b/attendance/views/penalty.py index eb88bbe0a..81d18e336 100644 --- a/attendance/views/penalty.py +++ b/attendance/views/penalty.py @@ -4,17 +4,18 @@ attendance/views/penalty.py This module is used to write late come early out penatly methods """ +from django.apps import apps from django.contrib import messages +from django.db.models.query import QuerySet from django.http import HttpResponse from django.shortcuts import redirect, render from django.utils.translation import gettext_lazy as _ -from attendance.filters import PenaltyFilter -from attendance.forms import PenaltyAccountForm -from attendance.models import AttendanceLateComeEarlyOut, PenaltyAccount -from employee.models import Employee +from attendance.models import AttendanceLateComeEarlyOut +from base.forms import PenaltyAccountForm +from base.models import PenaltyAccounts from horilla.decorators import hx_request_required, login_required, manager_can_enter -from leave.models import AvailableLeave +from horilla.methods import get_horilla_model_class @login_required @@ -34,21 +35,29 @@ def cut_available_leave(request, instance_id): previous_data = request_copy.urlencode() instance = AttendanceLateComeEarlyOut.objects.get(id=instance_id) form = PenaltyAccountForm(employee=instance.employee_id) - available = AvailableLeave.objects.filter(employee_id=instance.employee_id) + if apps.is_installed("leave"): + AvailableLeave = get_horilla_model_class( + app_label="leave", model="availableleave" + ) + available = AvailableLeave.objects.filter(employee_id=instance.employee_id) + else: + available = QuerySet().none() if request.method == "POST": form = PenaltyAccountForm(request.POST) if form.is_valid(): penalty_instance = form.instance - penalty = PenaltyAccount() - # late come early out id - penalty.late_early_id = instance - penalty.deduct_from_carry_forward = ( - penalty_instance.deduct_from_carry_forward - ) + penalty = PenaltyAccounts() penalty.employee_id = instance.employee_id - penalty.leave_type_id = penalty_instance.leave_type_id - penalty.minus_leaves = penalty_instance.minus_leaves + penalty.late_early_id = instance penalty.penalty_amount = penalty_instance.penalty_amount + + if apps.is_installed("leave"): + penalty.leave_type_id = penalty_instance.leave_type_id + penalty.minus_leaves = penalty_instance.minus_leaves + penalty.deduct_from_carry_forward = ( + penalty_instance.deduct_from_carry_forward + ) + penalty.save() messages.success(request, _("Penalty/Fine added")) form = PenaltyAccountForm() @@ -63,13 +72,3 @@ def cut_available_leave(request, instance_id): "pd": previous_data, }, ) - - -@login_required -@hx_request_required -def view_penalties(request): - """ - This method is used to filter or view the penalties - """ - records = PenaltyFilter(request.GET).qs - return render(request, "attendance/penalty/penalty_view.html", {"records": records}) diff --git a/attendance/views/requests.py b/attendance/views/requests.py index 1b8644cd8..0112fc0a3 100644 --- a/attendance/views/requests.py +++ b/attendance/views/requests.py @@ -22,10 +22,14 @@ from attendance.forms import ( BulkAttendanceRequestForm, NewRequestForm, ) -from attendance.methods.differentiate import get_diff_dict +from attendance.methods.utils import ( + get_diff_dict, + get_employee_last_name, + paginator_qry, + shift_schedule_today, +) from attendance.models import Attendance, AttendanceActivity, AttendanceLateComeEarlyOut from attendance.views.clock_in_out import early_out, late_come -from attendance.views.views import paginator_qry, shift_schedule_today from base.methods import ( choosesubordinates, closest_numbers, @@ -39,15 +43,6 @@ from horilla.decorators import hx_request_required, login_required, manager_can_ from notifications.signals import notify -def get_employee_last_name(attendance): - """ - This method is used to return the last name - """ - if attendance.employee_id.employee_last_name: - return attendance.employee_id.employee_last_name - return "" - - @login_required def request_attendance(request): """ diff --git a/attendance/views/search.py b/attendance/views/search.py index a0c418054..1b8429d78 100644 --- a/attendance/views/search.py +++ b/attendance/views/search.py @@ -8,6 +8,7 @@ import json from datetime import datetime from urllib.parse import parse_qs +from django.http import JsonResponse from django.shortcuts import render from django.utils.translation import gettext_lazy as _ @@ -478,9 +479,6 @@ def search_attendance_requests(request): ) -from django.http import JsonResponse - - @login_required def widget_filter(request): """ diff --git a/attendance/views/views.py b/attendance/views/views.py index 58ffa87b1..16bfa2626 100644 --- a/attendance/views/views.py +++ b/attendance/views/views.py @@ -22,7 +22,9 @@ from urllib.parse import parse_qs import pandas as pd from django.contrib import messages from django.core.paginator import Paginator +from django.core.validators import validate_ipv46_address from django.db.models import ProtectedError +from django.forms import ValidationError from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import redirect, render from django.urls import reverse @@ -52,6 +54,14 @@ from attendance.forms import ( GraceTimeForm, LateComeEarlyOutExportForm, ) +from attendance.methods.utils import ( + attendance_day_checking, + format_time, + is_reportingmanger, + monthly_leave_days, + paginator_qry, + strtime_seconds, +) from attendance.models import ( Attendance, AttendanceActivity, @@ -62,18 +72,30 @@ from attendance.models import ( AttendanceRequestFile, AttendanceValidationCondition, GraceTime, + WorkRecords, ) from attendance.views.handle_attendance_errors import handle_attendance_errors from attendance.views.process_attendance_data import process_attendance_data +from base.forms import ( + AttendanceAllowedIPForm, + AttendanceAllowedIPUpdateForm, + TrackLateComeEarlyOutForm, +) from base.methods import ( choosesubordinates, closest_numbers, export_data, filtersubordinates, get_key_instances, - get_pagination, ) -from base.models import EmployeeShiftSchedule +from base.models import ( + WEEK_DAYS, + AttendanceAllowedIP, + CompanyLeaves, + EmployeeShiftSchedule, + Holidays, + TrackLateComeEarlyOut, +) from employee.filters import EmployeeFilter from employee.models import Employee, EmployeeWorkInformation from horilla.decorators import ( @@ -83,61 +105,7 @@ from horilla.decorators import ( manager_can_enter, permission_required, ) -from leave.models import WEEK_DAYS, CompanyLeave, Holiday from notifications.signals import notify -from payroll.models.models import WorkRecord - -# Create your views here. - - -def intersection_list(list1, list2): - """ - This method is used to intersect two list - """ - return [value for value in list1 if value in list2] - - -def format_time(seconds): - """ - this method is used to formate seconds to H:M and return it - args: - seconds : seconds - """ - - hour = int(seconds // 3600) - minutes = int((seconds % 3600) // 60) - seconds = int((seconds % 3600) % 60) - return f"{hour:02d}:{minutes:02d}" - - -def strtime_seconds(time): - """ - this method is used reconvert time in H:M formate string back to seconds and return it - args: - time : time in H:M format - """ - - ftr = [3600, 60, 1] - return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) - - -def is_reportingmanger(request, instance): - """ - if the instance have employee id field then you can use this method to know the - request user employee is the reporting manager of the instance - args : - request : request - instance : an object or instance of any model contain employee_id foreign key field - """ - - manager = request.user.employee_get - try: - employee_workinfo_manager = ( - instance.employee_id.employee_work_info.reporting_manager_id - ) - except Exception: - return HttpResponse("This Employee Dont Have any work information") - return manager == employee_workinfo_manager def attendance_validate(attendance): @@ -157,60 +125,66 @@ def attendance_validate(attendance): return condition_for_at_work >= at_work -def attendance_day_checking(attendance_date, minimum_hour): - # Convert the string to a datetime object - attendance_datetime = datetime.strptime(attendance_date, "%Y-%m-%d") +@login_required +@hx_request_required +def profile_attendance_tab(request): + """ + This function is used to view attendance tab of an employee in profile view. - # Extract name of the day - attendance_day = attendance_datetime.strftime("%A") + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. - # Taking all holidays into a list - leaves = [] - holidays = Holiday.objects.all() - for holi in holidays: - start_date = holi.start_date - end_date = holi.end_date + Returns: return asset-request-tab template - # Convert start_date and end_date to datetime objects - start_date = datetime.strptime(str(start_date), "%Y-%m-%d") - end_date = datetime.strptime(str(end_date), "%Y-%m-%d") + """ + user = request.user + employee = user.employee_get + employee_attendances = employee.employee_attendances.all() + attendances_ids = json.dumps([instance.id for instance in employee_attendances]) + context = { + "attendances": employee_attendances, + "attendances_ids": attendances_ids, + } + return render(request, "tabs/profile-attendance-tab.html", context) - # Add dates in between start date and end date including both - current_date = start_date - while current_date <= end_date: - leaves.append(current_date.strftime("%Y-%m-%d")) - current_date += timedelta(days=1) - # Checking attendance date is in holiday list, if found making the minimum hour to 00:00 - for leave in leaves: - if str(leave) == str(attendance_date): - minimum_hour = "00:00" - break +@login_required +@manager_can_enter("employee.view_employee") +def attendance_tab(request, emp_id): + """ + This function is used to view attendance tab of an employee in individual view. - # Making a dictonary contains week day value and leave day pairs - company_leaves = {} - company_leave = CompanyLeave.objects.all() - for com_leave in company_leave: - a = dict(WEEK_DAYS).get(com_leave.based_on_week_day) - b = com_leave.based_on_week - company_leaves[b] = a + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. - # Checking the attendance date is in which week - week_in_month = str(((attendance_datetime.day - 1) // 7 + 1) - 1) + Returns: return attendance-tab template + """ - # Checking the attendance date is in the company leave or not - for pairs in company_leaves.items(): - # For all weeks based_on_week is None - if str(pairs[0]) == "None": - if str(pairs[1]) == str(attendance_day): - minimum_hour = "00:00" - break - # Checking with based_on_week and attendance_date week - if str(pairs[0]) == week_in_month: - if str(pairs[1]) == str(attendance_day): - minimum_hour = "00:00" - break - return minimum_hour + requests = Attendance.objects.filter( + is_validate_request=True, + employee_id=emp_id, + ) + attendances_ids = json.dumps([instance.id for instance in requests]) + validate_attendances = Attendance.objects.filter( + attendance_validated=False, employee_id=emp_id + ) + validate_attendances_ids = json.dumps( + [instance.id for instance in validate_attendances] + ) + accounts = AttendanceOverTime.objects.filter(employee_id=emp_id) + accounts_ids = json.dumps([instance.id for instance in accounts]) + + context = { + "requests": requests, + "attendances_ids": attendances_ids, + "accounts": accounts, + "accounts_ids": accounts_ids, + "validate_attendances": validate_attendances, + "validate_attendances_ids": validate_attendances_ids, + } + return render(request, "tabs/attendance-tab.html", context=context) @login_required @@ -237,22 +211,6 @@ def attendance_create(request): return render(request, "attendance/attendance/form.html", {"form": form}) -def get_record_per_page(): - """ - This method will return the record per page count - """ - return 50 - - -def paginator_qry(qryset, page_number): - """ - This method is used to paginate queryset - """ - paginator = Paginator(qryset, get_pagination()) - qryset = paginator.get_page(page_number) - return qryset - - def attendance_excel(_request): """ Generate an empty Excel template for attendance data with predefined columns. @@ -908,80 +866,6 @@ def attendance_activity_export(request): ) -def employee_exists(request): - """ - This method return the employee instance and work info if not exists return None instead - """ - employee, employee_work_info = None, None - try: - employee = request.user.employee_get - employee_work_info = employee.employee_work_info - finally: - return (employee, employee_work_info) - - -def shift_schedule_today(day, shift): - """ - This function is used to find shift schedules for the day, - it will returns min hour,start time seconds end time seconds - args: - shift : shift instance - day : shift day object - """ - schedule_today = day.day_schedule.filter(shift_id=shift) - start_time_sec, end_time_sec, minimum_hour = 0, 0, "00:00" - if schedule_today.exists(): - schedule_today = schedule_today[0] - minimum_hour = schedule_today.minimum_working_hour - start_time_sec = strtime_seconds(schedule_today.start_time.strftime("%H:%M")) - end_time_sec = strtime_seconds(schedule_today.end_time.strftime("%H:%M")) - return (minimum_hour, start_time_sec, end_time_sec) - - -def overtime_calculation(attendance): - """ - This method is used to calculate overtime of the attendance, it will - return difference between attendance worked hour and minimum hour if - and only worked hour greater than minimum hour, else return 00:00 - args: - attendance : attendance instance - """ - - minimum_hour = attendance.minimum_hour - at_work = attendance.attendance_worked_hour - at_work_sec = strtime_seconds(at_work) - minimum_hour_sec = strtime_seconds(minimum_hour) - if at_work_sec > minimum_hour_sec: - return format_time((at_work_sec - minimum_hour_sec)) - return "00:00" - - -def activity_datetime(attendance_activity): - """ - This method is used to convert clock-in and clock-out of activity as datetime object - args: - attendance_activity : attendance activity instance - """ - - # in - in_year = attendance_activity.clock_in_date.year - in_month = attendance_activity.clock_in_date.month - in_day = attendance_activity.clock_in_date.day - in_hour = attendance_activity.clock_in.hour - in_minute = attendance_activity.clock_in.minute - in_seconds = attendance_activity.clock_in.second - # out - out_year = attendance_activity.clock_out_date.year - out_month = attendance_activity.clock_out_date.month - out_day = attendance_activity.clock_out_date.day - out_hour = attendance_activity.clock_out.hour - out_minute = attendance_activity.clock_out.minute - out_seconds = attendance_activity.clock_out.second - return datetime( - in_year, in_month, in_day, in_hour, in_minute, in_seconds - ), datetime(out_year, out_month, out_day, out_hour, out_minute, out_seconds) - - @login_required def on_time_view(request): """ @@ -1986,45 +1870,6 @@ def delete_comment_file(request): ) -def monthly_leave_days(month, year): - leave_dates = [] - holidays = Holiday.objects.filter(start_date__month=month, start_date__year=year) - leave_dates += list(holidays.values_list("start_date", flat=True)) - - company_leaves = CompanyLeave.objects.all() - for company_leave in company_leaves: - year = year - month = month - based_on_week = company_leave.based_on_week - based_on_week_day = company_leave.based_on_week_day - if based_on_week != None: - calendar.setfirstweekday(6) - month_calendar = calendar.monthcalendar(year, month) - weeks = month_calendar[int(based_on_week)] - weekdays_in_weeks = [day for day in weeks if day != 0] - for day in weekdays_in_weeks: - date_name = datetime.strptime( - f"{year}-{month:02}-{day:02}", "%Y-%m-%d" - ).date() - if ( - date_name.weekday() == int(based_on_week_day) - and date_name not in leave_dates - ): - leave_dates.append(date_name) - else: - calendar.setfirstweekday(0) - month_calendar = calendar.monthcalendar(year, month) - for week in month_calendar: - if week[int(based_on_week_day)] != 0: - date_name = datetime.strptime( - f"{year}-{month:02}-{week[int(based_on_week_day)]:02}", - "%Y-%m-%d", - ).date() - if date_name not in leave_dates: - leave_dates.append(date_name) - return leave_dates - - @login_required def work_records(request): today = date.today() @@ -2063,7 +1908,7 @@ def work_records_change_month(request): days = [day for week in month_matrix for day in week if day != 0] current_month_date_list = [datetime(year, month, day).date() for day in days] - all_work_records = WorkRecord.objects.filter( + all_work_records = WorkRecords.objects.filter( date__in=current_month_date_list ).select_related("employee_id") @@ -2118,11 +1963,11 @@ def work_records_change_month(request): @login_required -@permission_required("leave.add_leaverequest") +@permission_required("attendance.view_workrecords") def work_record_export(request): month = int(request.GET.get("month", date.today().month)) year = int(request.GET.get("year", date.today().year)) - records = WorkRecord.objects.filter(date__month=month, date__year=year) + records = WorkRecords.objects.filter(date__month=month, date__year=year) num_days = calendar.monthrange(year, month)[1] all_date_objects = [date(year, month, day) for day in range(1, num_days + 1)] leave_dates = monthly_leave_days(month, year) @@ -2243,3 +2088,259 @@ def enable_timerunner(request): time_runner.time_runner = "time_runner" in request.GET.keys() time_runner.save() return HttpResponse("success") + + +@login_required +@permission_required("attendance.view_attendancevalidationcondition") +def validation_condition_view(request): + """ + This method view attendance validation conditions. + """ + + condition = AttendanceValidationCondition.objects.first() + default_grace_time = GraceTime.objects.filter(is_default=True).first() + return render( + request, + "attendance/break_point/condition.html", + {"condition": condition, "default_grace_time": default_grace_time}, + ) + + +@login_required +@permission_required("base.view_tracklatecomeearlyout") +def track_late_come_early_out(request): + """ + Renders the form to track late arrivals and early departures in attendance. + """ + tracking = TrackLateComeEarlyOut.objects.first() + form = TrackLateComeEarlyOutForm( + initial={"is_enable": tracking.is_enable} if tracking else {} + ) + return render( + request, "attendance/late_come_early_out/tracking.html", {"form": form} + ) + + +@login_required +@permission_required("base.change_tracklatecomeearlyout") +def enable_disable_tracking_late_come_early_out(request): + """ + Enables or disables the tracking of late arrivals and early departures in attendance. + """ + if request.method == "POST": + enable = bool(request.POST.get("is_enable")) + tracking, created = TrackLateComeEarlyOut.objects.get_or_create() + tracking.is_enable = enable + tracking.save() + message = _("enabled") if enable else _("disabled") + messages.success( + request, _("Tracking late come early out {} successfully").format(message) + ) + return HttpResponse("") + + +@login_required +@permission_required("attendance.view_attendancevalidationcondition") +def grace_time_view(request): + """ + This method view attendance validation conditions. + """ + condition = AttendanceValidationCondition.objects.first() + default_grace_time = GraceTime.objects.filter(is_default=True).first() + grace_times = GraceTime.objects.all().exclude(is_default=True) + return render( + request, + "attendance/grace_time/grace_time.html", + { + "condition": condition, + "default_grace_time": default_grace_time, + "grace_times": grace_times, + }, + ) + + +@login_required +@permission_required("attendance.add_attendancevalidationcondition") +def validation_condition_create(request): + """ + This method render a form to create attendance validation conditions, + and create if the form is valid. + """ + form = AttendanceValidationConditionForm() + if request.method == "POST": + form = AttendanceValidationConditionForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, _("Attendance Break-point settings created.")) + return HttpResponse("") + return render( + request, + "attendance/break_point/condition_form.html", + {"form": form}, + ) + + +@login_required +@hx_request_required +@permission_required("attendance.change_attendancevalidationcondition") +def validation_condition_update(request, obj_id): + """ + This method is used to update validation condition + Args: + obj_id : validation condition instance id + """ + condition = AttendanceValidationCondition.objects.get(id=obj_id) + form = AttendanceValidationConditionForm(instance=condition) + if request.method == "POST": + form = AttendanceValidationConditionForm(request.POST, instance=condition) + if form.is_valid(): + form.save() + messages.success(request, _("Attendance Break-point settings updated.")) + return HttpResponse("") + return render( + request, + "attendance/break_point/condition_form.html", + {"form": form, "condition": condition}, + ) + + +@login_required +@permission_required("attendance.add_attendance") +def allowed_ips(request): + """ + This function is used to view the allowed ips + """ + allowed_ips = AttendanceAllowedIP.objects.first() + return render( + request, + "attendance/ip_restriction/ip_restriction.html", + {"allowed_ips": allowed_ips}, + ) + + +@login_required +@permission_required("attendance.add_attendance") +def enable_ip_restriction(request): + """ + This function is used to enable the allowed ips + """ + form = AttendanceAllowedIPForm() + if request.method == "POST": + ip_restiction = AttendanceAllowedIP.objects.first() + + if not ip_restiction: + ip_restiction = AttendanceAllowedIP.objects.create(is_enabled=True) + return HttpResponse("") + + if not ip_restiction.is_enabled: + ip_restiction.is_enabled = True + elif ip_restiction.is_enabled: + ip_restiction.is_enabled = False + + ip_restiction.save() + return HttpResponse("") + + +def validate_ip_address(self, value): + """ + This function is used to check if the provided IP is in the ipv4 or ipv6 format. + + Args: + value: The IP address to validate + """ + try: + validate_ipv46_address(value) + except ValidationError: + raise ValidationError("Enter a valid IPv4 or IPv6 address.") + return value + + +@login_required +@permission_required("attendance.add_attendance") +def create_allowed_ips(request): + """ + This function is used to create the allowed ips + """ + form = AttendanceAllowedIPForm() + if request.method == "POST": + form = AttendanceAllowedIPForm(request.POST) + if form.is_valid(): + values = [request.POST[key] for key in request.POST.keys()] + allowed_ips = AttendanceAllowedIP.objects.first() + for value in values: + try: + validate_ipv46_address(value) + if value not in allowed_ips.additional_data["allowed_ips"]: + allowed_ips.additional_data["allowed_ips"].append(value) + messages.success(request, f"IP address saved successfully") + else: + messages.error(request, "IP address already exists") + + except ValidationError: + messages.error( + request, f"Enter a valid IPv4 or IPv6 address: {value}" + ) + + allowed_ips.save() + + return HttpResponse("") + return render( + request, "attendance/ip_restriction/restrict_form.html", {"form": form} + ) + + +@login_required +@permission_required("attendance.delete_attendance") +def delete_allowed_ips(request): + """ + This function is used to delete the allowed ips + """ + try: + ids = request.GET.getlist("id") + allowed_ips = AttendanceAllowedIP.objects.first() + ips = allowed_ips.additional_data["allowed_ips"] + for id in ids: + ips.pop(eval(id)) + + allowed_ips.additional_data["allowed_ips"] = ips + allowed_ips.save() + + messages.success(request, "IP address removed successfully") + except: + messages.error(request, "Invalid id") + return redirect("allowed-ips") + + +@login_required +@permission_required("attendance.change_attendance") +def edit_allowed_ips(request): + """ + This function is used to edit the allowed ips + """ + try: + + allowed_ips = AttendanceAllowedIP.objects.first() + ips = allowed_ips.additional_data["allowed_ips"] + id = request.GET.get("id") + + form = AttendanceAllowedIPUpdateForm(initial={"ip_address": ips[eval(id)]}) + if request.method == "POST": + form = AttendanceAllowedIPUpdateForm(request.POST) + if form.is_valid(): + new_ip = form.cleaned_data["ip_address"] + ips[eval(id)] = new_ip + if not new_ip in allowed_ips.additional_data["allowed_ips"]: + allowed_ips.additional_data["allowed_ips"] = ips + allowed_ips.save() + messages.success(request, "IP address updated successfully") + else: + messages.error(request, "IP address already exists") + + return HttpResponse("") + except: + messages.error(request, "Invalid id") + return render( + request, + "attendance/ip_restriction/restrict_update_form.html", + {"form": form, "id": id}, + ) diff --git a/base/admin.py b/base/admin.py index 492426d06..9ceef5fae 100644 --- a/base/admin.py +++ b/base/admin.py @@ -11,6 +11,7 @@ from base.models import ( Announcement, Attachment, Company, + CompanyLeaves, DashboardEmployeeCharts, Department, DynamicEmailConfiguration, @@ -20,9 +21,11 @@ from base.models import ( EmployeeShiftDay, EmployeeShiftSchedule, EmployeeType, + Holidays, JobPosition, JobRole, MultipleApprovalManagers, + PenaltyAccounts, RotatingShift, RotatingShiftAssign, RotatingWorkType, @@ -62,3 +65,6 @@ admin.site.register(Announcement) admin.site.register(Attachment) admin.site.register(EmailLog) admin.site.register(DashboardEmployeeCharts) +admin.site.register(Holidays) +admin.site.register(CompanyLeaves) +admin.site.register(PenaltyAccounts) diff --git a/base/context_processors.py b/base/context_processors.py index 7fb9da00f..ae62bde65 100644 --- a/base/context_processors.py +++ b/base/context_processors.py @@ -4,18 +4,16 @@ context_processor.py This module is used to register context processor` """ +from django.apps import apps from django.http import HttpResponse from django.urls import path -from attendance.models import AttendanceGeneralSetting from base.models import Company, TrackLateComeEarlyOut from base.urls import urlpatterns from employee.models import EmployeeGeneralSetting from horilla import horilla_apps from horilla.decorators import hx_request_required, login_required, permission_required -from offboarding.models import OffboardingGeneralSetting -from payroll.models.models import PayrollGeneralSetting -from recruitment.models import RecruitmentGeneralSetting +from horilla.methods import get_horilla_model_class class AllCompany: @@ -132,8 +130,13 @@ def resignation_request_enabled(request): """ Check weather resignation_request enabled of not in offboarding """ - first = OffboardingGeneralSetting.objects.first() enabled_resignation_request = False + first = None + if apps.is_installed("offboarding"): + OffboardingGeneralSetting = get_horilla_model_class( + app_label="offboarding", model="offboardinggeneralsetting" + ) + first = OffboardingGeneralSetting.objects.first() if first: enabled_resignation_request = first.resignation_request return {"enabled_resignation_request": enabled_resignation_request} @@ -143,8 +146,13 @@ def timerunner_enabled(request): """ Check weather resignation_request enabled of not in offboarding """ - first = AttendanceGeneralSetting.objects.first() + first = None enabled_timerunner = True + if apps.is_installed("attendance"): + AttendanceGeneralSetting = get_horilla_model_class( + app_label="attendance", model="attendancegeneralsetting" + ) + first = AttendanceGeneralSetting.objects.first() if first: enabled_timerunner = first.time_runner return {"enabled_timerunner": enabled_timerunner} @@ -154,8 +162,13 @@ def intial_notice_period(request): """ Check weather resignation_request enabled of not in offboarding """ - first = PayrollGeneralSetting.objects.first() initial = 30 + first = None + if apps.is_installed("payroll"): + PayrollGeneralSetting = get_horilla_model_class( + app_label="payroll", model="payrollgeneralsetting" + ) + first = PayrollGeneralSetting.objects.first() if first: initial = first.notice_period return {"get_initial_notice_period": initial} @@ -165,8 +178,15 @@ def check_candidate_self_tracking(request): """ This method is used to get the candidate self tracking is enabled or not """ - first = RecruitmentGeneralSetting.objects.first() + candidate_self_tracking = False + if apps.is_installed("recruitment"): + RecruitmentGeneralSetting = get_horilla_model_class( + app_label="recruitment", model="recruitmentgeneralsetting" + ) + first = RecruitmentGeneralSetting.objects.first() + else: + first = None if first: candidate_self_tracking = first.candidate_self_tracking return {"check_candidate_self_tracking": candidate_self_tracking} @@ -176,8 +196,14 @@ def check_candidate_self_tracking_rating(request): """ This method is used to check enabled/disabled of rating option """ - first = RecruitmentGeneralSetting.objects.first() rating_option = False + if apps.is_installed("recruitment"): + RecruitmentGeneralSetting = get_horilla_model_class( + app_label="recruitment", model="recruitmentgeneralsetting" + ) + first = RecruitmentGeneralSetting.objects.first() + else: + first = None if first: rating_option = first.show_overall_rating return {"check_candidate_self_tracking_rating": rating_option} diff --git a/base/filters.py b/base/filters.py index 39ef976a1..bf1e168e1 100644 --- a/base/filters.py +++ b/base/filters.py @@ -7,9 +7,13 @@ import uuid import django_filters from django import forms -from django_filters import CharFilter +from django.utils.translation import gettext as __ +from django_filters import CharFilter, DateFilter, FilterSet, NumberFilter, filters from base.models import ( + CompanyLeaves, + Holidays, + PenaltyAccounts, RotatingShiftAssign, RotatingWorkTypeAssign, ShiftRequest, @@ -279,3 +283,116 @@ class RotatingShiftRequestReGroup: ("employee_id__employee_work_info__job_role_id", "Job Role"), ("employee_id__employee_work_info__reporting_manager_id", "Reporting Manager"), ] + + +class HolidayFilter(FilterSet): + """ + Filter class for Holidays model. + + This filter allows searching Holidays objects based on name and date range. + """ + + search = filters.CharFilter(field_name="name", lookup_expr="icontains") + from_date = DateFilter( + field_name="start_date", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "date"}), + ) + to_date = DateFilter( + field_name="end_date", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "date"}), + ) + + start_date = DateFilter( + field_name="start_date", + lookup_expr="exact", + widget=forms.DateInput(attrs={"type": "date"}), + ) + + end_date = DateFilter( + field_name="end_date", + lookup_expr="exact", + widget=forms.DateInput(attrs={"type": "date"}), + ) + + class Meta: + """ + Meta class defines the model and fields to filter + """ + + model = Holidays + fields = { + "recurring": ["exact"], + } + + def __init__(self, data=None, queryset=None, *, request=None, prefix=None): + super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) + for field in self.form.fields.keys(): + self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" + + +class CompanyLeaveFilter(FilterSet): + """ + Filter class for CompanyLeaves model. + + This filter allows searching CompanyLeaves objects based on + name, week day and based_on_week choices. + """ + + name = filters.CharFilter(field_name="based_on_week_day", lookup_expr="icontains") + search = filters.CharFilter(method="filter_week_day") + + class Meta: + """ " + Meta class defines the model and fields to filter + """ + + model = CompanyLeaves + fields = { + "based_on_week": ["exact"], + "based_on_week_day": ["exact"], + } + + def filter_week_day(self, queryset, _, value): + week_qry = CompanyLeaves.objects.none() + weekday_values = [] + week_values = [] + WEEK_DAYS = [ + ("0", __("Monday")), + ("1", __("Tuesday")), + ("2", __("Wednesday")), + ("3", __("Thursday")), + ("4", __("Friday")), + ("5", __("Saturday")), + ("6", __("Sunday")), + ] + WEEKS = [ + (None, __("All")), + ("0", __("First Week")), + ("1", __("Second Week")), + ("2", __("Third Week")), + ("3", __("Fourth Week")), + ("4", __("Fifth Week")), + ] + + for day_value, day_name in WEEK_DAYS: + if value.lower() in day_name.lower(): + weekday_values.append(day_value) + for day_value, day_name in WEEKS: + if value.lower() in day_name.lower() and value.lower() != __("All").lower(): + week_values.append(day_value) + week_qry = queryset.filter(based_on_week__in=week_values) + elif value.lower() in __("All").lower(): + week_qry = queryset.filter(based_on_week__isnull=True) + return queryset.filter(based_on_week_day__in=weekday_values) | week_qry + + +class PenaltyFilter(FilterSet): + """ + PenaltyFilter + """ + + class Meta: + model = PenaltyAccounts + fields = "__all__" diff --git a/base/forms.py b/base/forms.py index c7bdbbeaf..aa3e1568c 100644 --- a/base/forms.py +++ b/base/forms.py @@ -12,6 +12,7 @@ from datetime import date, timedelta from typing import Any from django import forms +from django.apps import apps from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.forms import SetPasswordForm, _unicode_ci_compare @@ -38,6 +39,7 @@ from base.models import ( AttendanceAllowedIP, BaserequestFile, Company, + CompanyLeaves, Department, DriverViewed, DynamicEmailConfiguration, @@ -46,9 +48,12 @@ from base.models import ( EmployeeShiftDay, EmployeeShiftSchedule, EmployeeType, + Holidays, + HorillaMailTemplate, JobPosition, JobRole, MultipleApprovalCondition, + PenaltyAccounts, RotatingShift, RotatingShiftAssign, RotatingWorkType, @@ -63,9 +68,10 @@ from base.models import ( ) from employee.filters import EmployeeFilter from employee.forms import MultipleFileField -from employee.models import Employee, EmployeeTag +from employee.models import Employee from horilla import horilla_middlewares from horilla.horilla_middlewares import _thread_locals +from horilla.methods import get_horilla_model_class from horilla_audit.models import AuditTag from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget @@ -1527,7 +1533,7 @@ class ShiftRequestForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html def save(self, commit: bool = ...): @@ -1590,7 +1596,7 @@ class ShiftAllocationForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html def save(self, commit: bool = ...): @@ -1639,7 +1645,7 @@ class WorkTypeRequestForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html def save(self, commit: bool = ...): @@ -1891,26 +1897,10 @@ class TagsForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html -class EmployeeTagForm(ModelForm): - """ - Employee Tags form - """ - - class Meta: - """ - Meta class for additional options - """ - - model = EmployeeTag - fields = "__all__" - exclude = ["is_active"] - widgets = {"color": TextInput(attrs={"type": "color", "style": "height:50px"})} - - class AuditTagForm(ModelForm): """ Audit Tags form @@ -1971,7 +1961,7 @@ class DynamicMailConfForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html @@ -1983,6 +1973,46 @@ class DynamicMailTestForm(forms.Form): to_email = forms.EmailField(label="To email", required=True) +class MailTemplateForm(ModelForm): + """ + MailTemplateForm + """ + + class Meta: + model = HorillaMailTemplate + fields = "__all__" + widgets = { + "body": forms.Textarea( + attrs={"data-summernote": "", "style": "display:none;"} + ), + } + + def get_template_language(self): + mail_data = { + "Receiver|Full name": "instance.get_full_name", + "Sender|Full name": "self.get_full_name", + "Receiver|Recruitment": "instance.recruitment_id", + "Sender|Recruitment": "self.recruitment_id", + "Receiver|Company": "instance.get_company", + "Sender|Company": "self.get_company", + "Receiver|Job position": "instance.get_job_position", + "Sender|Job position": "self.get_job_position", + "Receiver|Email": "instance.get_mail", + "Sender|Email": "self.get_mail", + "Receiver|Employee Type": "instance.get_employee_type", + "Sender|Employee Type": "self.get_employee_type", + "Receiver|Work Type": "instance.get_work_type", + "Sender|Work Type": "self.get_work_type", + "Candidate|Full name": "instance.get_full_name", + "Candidate|Recruitment": "instance.recruitment_id", + "Candidate|Company": "instance.get_company", + "Candidate|Job position": "instance.get_job_position", + "Candidate|Email": "instance.get_email", + "Candidate|Interview Table": "instance.get_interview|safe", + } + return mail_data + + class MultipleApproveConditionForm(ModelForm): CONDITION_CHOICE = [ ("equal", _("Equal (==)")), @@ -2315,8 +2345,135 @@ class TrackLateComeEarlyOutForm(ModelForm): super().__init__(*args, **kwargs) self.fields["is_enable"].widget.attrs.update( { - "hx-post": "/settings/enable-disable-tracking-late-come-early-out", + "hx-post": "enable-disable-tracking-late-come-early-out", "hx-target": "this", "hx-trigger": "change", } ) + + +class HolidayForm(ModelForm): + """ + Form for creating or updating a holiday. + + This form allows users to create or update holiday data by specifying details such as + the start date and end date. + + Attributes: + - start_date: A DateField representing the start date of the holiday. + - end_date: A DateField representing the end date of the holiday. + """ + + start_date = forms.DateField( + widget=forms.DateInput(attrs={"type": "date"}), + ) + end_date = forms.DateField( + widget=forms.DateInput(attrs={"type": "date"}), + ) + + def clean_end_date(self): + start_date = self.cleaned_data.get("start_date") + end_date = self.cleaned_data.get("end_date") + + if start_date and end_date and end_date < start_date: + raise ValidationError( + _("End date should not be earlier than the start date.") + ) + + return end_date + + class Meta: + """ + Meta class for additional options + """ + + model = Holidays + fields = "__all__" + exclude = ["is_active"] + labels = { + "name": _("Name"), + } + + def __init__(self, *args, **kwargs): + super(HolidayForm, self).__init__(*args, **kwargs) + self.fields["name"].widget.attrs["autocomplete"] = "name" + + +class HolidaysColumnExportForm(forms.Form): + """ + Form for selecting columns to export in holiday data. + + This form allows users to select specific columns from the Holidays model + for export. The available columns are dynamically generated based on the + model's meta information, excluding specified excluded_fields. + + Attributes: + - model_fields: A list of fields in the Holidays model. + - field_choices: A list of field choices for the form, consisting of field names + and their verbose names, excluding specified excluded_fields. + - selected_fields: A MultipleChoiceField representing the selected columns + to be exported. + """ + + model_fields = Holidays._meta.get_fields() + field_choices = [ + (field.name, field.verbose_name) + for field in model_fields + if hasattr(field, "verbose_name") and field.name not in excluded_fields + ] + selected_fields = forms.MultipleChoiceField( + choices=field_choices, + widget=forms.CheckboxSelectMultiple, + initial=[ + "name", + "start_date", + "end_date", + "recurring", + ], + ) + + +class CompanyLeaveForm(ModelForm): + """ + Form for managing company leave data. + + This form allows users to manage company leave data by including all fields from + the CompanyLeaves model except for is_active. + + Attributes: + - Meta: Inner class defining metadata options. + - model: The model associated with the form (CompanyLeaves). + - fields: A special value indicating all fields should be included in the form. + - exclude: A list of fields to exclude from the form (is_active). + """ + + class Meta: + """ + Meta class for additional options + """ + + model = CompanyLeaves + fields = "__all__" + exclude = ["is_active"] + + +class PenaltyAccountForm(ModelForm): + """ + PenaltyAccountForm + """ + + class Meta: + model = PenaltyAccounts + fields = "__all__" + exclude = ["is_active"] + + def __init__(self, *args, **kwargs): + employee = kwargs.pop("employee", None) + super().__init__(*args, **kwargs) + if apps.is_installed("leave") and employee: + LeaveType = get_horilla_model_class(app_label="leave", model="leavetype") + available_leaves = employee.available_leave.all() + assigned_leave_types = LeaveType.objects.filter( + id__in=available_leaves.values_list("leave_type_id", flat=True) + ) + self.fields["leave_type_id"].queryset = assigned_leave_types diff --git a/base/methods.py b/base/methods.py index 3b5d1e453..cffe10250 100644 --- a/base/methods.py +++ b/base/methods.py @@ -1,8 +1,9 @@ +import calendar import io import json import os import random -from datetime import date, datetime, time +from datetime import date, datetime, time, timedelta import pandas as pd from django.apps import apps @@ -10,7 +11,7 @@ from django.conf import settings from django.contrib.staticfiles import finders from django.core.exceptions import ObjectDoesNotExist from django.db import models -from django.db.models import F, ForeignKey, ManyToManyField, OneToOneField +from django.db.models import F, ForeignKey, ManyToManyField, OneToOneField, Q from django.db.models.functions import Lower from django.forms.models import ModelChoiceField from django.http import HttpResponse @@ -18,11 +19,9 @@ from django.template.loader import get_template, render_to_string from django.utils.translation import gettext as _ from xhtml2pdf import pisa -from base.models import Company, DynamicPagination +from base.models import Company, CompanyLeaves, DynamicPagination, Holidays from employee.models import Employee, EmployeeWorkInformation from horilla.decorators import login_required -from leave.models import LeaveRequest, LeaveRequestConditionApproval -from recruitment.models import Candidate def filtersubordinates(request, queryset, perm=None, field=None): @@ -586,15 +585,21 @@ def reload_queryset(fields): """ This method is used to reload the querysets in the form """ - for k, v in fields.items(): - if isinstance(v, ModelChoiceField): - if v.queryset.model == Employee: - v.queryset = v.queryset.model.objects.filter(is_active=True) - elif v.queryset.model == Candidate: - v.queryset = v.queryset.model.objects.filter(is_active=True) + model_filters = { + "Employee": {"is_active": True}, + "Candidate": {"is_active": True} if apps.is_installed("recruitment") else None, + } + + for field in fields.values(): + if isinstance(field, ModelChoiceField): + model_name = field.queryset.model.__name__ + filter_criteria = model_filters.get(model_name) + if filter_criteria is not None: + field.queryset = field.queryset.model.objects.filter(**filter_criteria) else: - v.queryset = v.queryset.model.objects.all() - return + field.queryset = field.queryset.model.objects.all() + + return fields def check_manager(employee, instance): @@ -680,26 +685,6 @@ def generate_pdf(template_path, context, path=True, title=None, html=True): return response -def filter_conditional_leave_request(request): - approval_manager = Employee.objects.filter(employee_user_id=request.user).first() - leave_request_ids = [] - multiple_approval_requests = LeaveRequestConditionApproval.objects.filter( - manager_id=approval_manager - ) - for instance in multiple_approval_requests: - if instance.sequence > 1: - pre_sequence = instance.sequence - 1 - leave_request_id = instance.leave_request_id - instance = LeaveRequestConditionApproval.objects.filter( - leave_request_id=leave_request_id, sequence=pre_sequence - ).first() - if instance and instance.is_approved: - leave_request_ids.append(instance.leave_request_id.id) - else: - leave_request_ids.append(instance.leave_request_id.id) - return LeaveRequest.objects.filter(pk__in=leave_request_ids) - - def get_pagination(): from horilla.horilla_middlewares import _thread_locals @@ -710,3 +695,200 @@ def get_pagination(): if page: count = page.pagination return count + + +def is_holiday(date): + """ + Check if the given date is a holiday. + Args: + date (datetime.date): The date to check. + Returns: + Holidays or bool: The Holidays object if the date is a holiday, otherwise False. + """ + holidays = Holidays.objects.all() + for holiday in holidays: + start_date = holiday.start_date + end_date = holiday.end_date + # Check if the date is within the range of the holiday dates + if start_date <= date <= end_date: + return holiday + # Check for recurring holidays + if holiday.recurring: + try: + # Create a new date object for comparison without the year + start_date_without_year = datetime( + year=date.year, month=start_date.month, day=start_date.day + ).date() + end_date_without_year = datetime( + year=date.year, month=end_date.month, day=end_date.day + ).date() + if start_date_without_year <= date <= end_date_without_year: + return holiday + except: + return False + return False + + +def is_company_leave(input_date): + """ + Check if the given date is a company leave. + Args: + input_date (datetime.date): The date to check. + Returns: + CompanyLeaves or bool: The CompanyLeaves object if the date is a company leave, otherwise False. + """ + # Calculate the week number within the month (1-5) + first_day_of_month = input_date.replace(day=1) + first_week_day = first_day_of_month.weekday() # Monday is 0 and Sunday is 6 + adjusted_day = input_date.day + first_week_day + date_week_no = (adjusted_day - 1) // 7 + # Calculate the weekday (1 for Monday to 7 for Sunday) + date_week_day = input_date.isoweekday() - 1 + company_leaves = CompanyLeaves.objects.all() + for company_leave in company_leaves: + week_no = ( + company_leave.based_on_week + if not company_leave.based_on_week + else int(company_leave.based_on_week) + ) # from 0 for the first week to 4 for the fifth week + week_day = int( + company_leave.based_on_week_day + ) # from 0 to 6 for Monday to Sunday + if not week_no: + if date_week_day == week_day: + return company_leave + if date_week_no == week_no and date_week_day == week_day: + return company_leave + return False + + +def get_date_range(start_date, end_date): + """ + Returns a list of all dates within a given date range. + + Args: + start_date (date): The start date of the range. + end_date (date): The end date of the range. + + Returns: + list: A list of date objects representing all dates within the range. + + Example: + start_date = date(2023, 1, 1) + end_date = date(2023, 1, 10) + date_range = get_date_range(start_date, end_date) + for date_obj in date_range: + print(date_obj) + """ + date_list = [] + delta = end_date - start_date + + for i in range(delta.days + 1): + current_date = start_date + timedelta(days=i) + date_list.append(current_date) + return date_list + + +def get_holiday_dates(range_start: date, range_end: date) -> list: + """ + :return: this functions returns a list of all holiday dates. + """ + pay_range_dates = get_date_range(start_date=range_start, end_date=range_end) + query = Q() + for check_date in pay_range_dates: + query |= Q(start_date__lte=check_date, end_date__gte=check_date) + holidays = Holidays.objects.filter(query) + holiday_dates = set([]) + for holiday in holidays: + holiday_dates = holiday_dates | ( + set( + get_date_range(start_date=holiday.start_date, end_date=holiday.end_date) + ) + ) + return list(set(holiday_dates)) + + +def get_company_leave_dates(year): + """ + :return: This function returns a list of all company leave dates + """ + company_leaves = CompanyLeaves.objects.all() + company_leave_dates = [] + for company_leave in company_leaves: + based_on_week = company_leave.based_on_week + based_on_week_day = company_leave.based_on_week_day + for month in range(1, 13): + if based_on_week is not None: + # Set Sunday as the first day of the week + calendar.setfirstweekday(6) + month_calendar = calendar.monthcalendar(year, month) + weeks = month_calendar[int(based_on_week)] + weekdays_in_weeks = [day for day in weeks if day != 0] + for day in weekdays_in_weeks: + leave_date = datetime.strptime( + f"{year}-{month:02}-{day:02}", "%Y-%m-%d" + ).date() + if ( + leave_date.weekday() == int(based_on_week_day) + and leave_date not in company_leave_dates + ): + company_leave_dates.append(leave_date) + else: + # Set Monday as the first day of the week + calendar.setfirstweekday(0) + month_calendar = calendar.monthcalendar(year, month) + for week in month_calendar: + if week[int(based_on_week_day)] != 0: + leave_date = datetime.strptime( + f"{year}-{month:02}-{week[int(based_on_week_day)]:02}", + "%Y-%m-%d", + ).date() + if leave_date not in company_leave_dates: + company_leave_dates.append(leave_date) + return company_leave_dates + + +def get_working_days(start_date, end_date): + """ + This method is used to calculate the total working days, total leave, worked days on that period + + Args: + start_date (_type_): the start date from the data needed + end_date (_type_): the end date till the date needed + """ + + holiday_dates = get_holiday_dates(start_date, end_date) + + # appending company/holiday leaves + # Note: Duplicate entry may exist + company_leave_dates = ( + list( + set( + get_company_leave_dates(start_date.year) + + get_company_leave_dates(end_date.year) + ) + ) + + holiday_dates + ) + + date_range = get_date_range(start_date, end_date) + + # making unique list of company/holiday leave dates then filtering + # the leave dates only between the start and end date + company_leave_dates = [ + date + for date in list(set(company_leave_dates)) + if start_date <= date <= end_date + ] + + working_days_between_ranges = list(set(date_range) - set(company_leave_dates)) + total_working_days = len(working_days_between_ranges) + + return { + # Total working days on that period + "total_working_days": total_working_days, + # All the working dates between the start and end date + "working_days_on": working_days_between_ranges, + # All the company/holiday leave dates between the range + "company_leave_dates": company_leave_dates, + } diff --git a/base/migrations/__init__.py b/base/migrations/__init__.py index e69de29bb..19cd0e37c 100644 --- a/base/migrations/__init__.py +++ b/base/migrations/__init__.py @@ -0,0 +1,144 @@ +from django.apps import apps + +try: + RecruitmentMailTemplate = apps.get_model("recruitment", "RecruitmentMailTemplate") + HorillaMailTemplate = apps.get_model("base", "HorillaMailTemplate") + + recruitment_mail_templates = RecruitmentMailTemplate.objects.all() + for recruitment_mail in recruitment_mail_templates: + if not HorillaMailTemplate.objects.filter( + title=recruitment_mail.title + ).exists(): + horilla_mail = HorillaMailTemplate( + id=recruitment_mail.id, + title=recruitment_mail.title, + body=recruitment_mail.body, + company_id=recruitment_mail.company_id, + ) + horilla_mail.save() + + horilla_mail_templates = HorillaMailTemplate.objects.all() +except Exception as e: + pass + +try: + LeaveHoliday = apps.get_model("leave", "Holiday") + BaseHoliday = apps.get_model("base", "Holidays") + + leave_holidays = LeaveHoliday.objects.all() + for holiday in leave_holidays: + if not BaseHoliday.objects.filter( + name=holiday.name, + start_date=holiday.start_date, + end_date=holiday.end_date, + ).exists(): + horilla = BaseHoliday( + id=holiday.id, + name=holiday.name, + start_date=holiday.start_date, + end_date=holiday.end_date, + ) + horilla.save() + + base_leaves = BaseHoliday.objects.all() +except Exception as e: + pass + +try: + PenaltyAccount = apps.get_model("attendance", "PenaltyAccount") + PenaltyAccounts = apps.get_model("base", "PenaltyAccounts") + + penalties = PenaltyAccount.objects.all() + for penalty in penalties: + filter_conditions = { + "employee_id": penalty.employee_id, + "penalty_amount": penalty.penalty_amount, + } + if apps.is_installed("attendance"): + filter_conditions.update( + { + "late_early_id": penalty.late_early_id, + } + ) + if apps.is_installed("leave"): + filter_conditions.update( + { + "leave_request_id": penalty.leave_request_id, + "leave_type_id": penalty.leave_type_id, + "minus_leaves": penalty.minus_leaves, + "deduct_from_carry_forward": penalty.deduct_from_carry_forward, + } + ) + + if not PenaltyAccounts.objects.filter(**filter_conditions).exists(): + horilla = PenaltyAccounts( + id=penalty.id, + employee_id=penalty.employee_id, + penalty_amount=penalty.penalty_amount, + ) + if apps.is_installed("attendance"): + horilla.late_early_id = penalty.late_early_id + if apps.is_installed("leave"): + horilla.leave_request_id = penalty.leave_request_id + horilla.leave_type_id = penalty.leave_type_id + horilla.minus_leaves = penalty.minus_leaves + horilla.deduct_from_carry_forward = penalty.deduct_from_carry_forward + horilla.save() + penalty_accounts = PenaltyAccounts.objects.all() +except Exception as e: + pass + +try: + CompanyLeave = apps.get_model("leave", "CompanyLeave") + BaseCompanyLeave = apps.get_model("base", "CompanyLeaves") + + company_leaves = CompanyLeave.objects.all() + for leave in company_leaves: + if not BaseCompanyLeave.objects.filter( + based_on_week=leave.based_on_week, + based_on_week_day=leave.based_on_week_day, + ).exists(): + horilla = BaseCompanyLeave( + id=leave.id, + based_on_week=leave.based_on_week, + based_on_week_day=leave.based_on_week_day, + ) + horilla.save() + + base_leaves = BaseCompanyLeave.objects.all() +except Exception as e: + pass + +try: + WorkRecord = apps.get_model("payroll", "WorkRecord") + WorkRecords = apps.get_model("attendance", "WorkRecords") + + work_records = WorkRecord.objects.all() + for work_record in work_records: + if not WorkRecords.objects.filter( + record_name=work_record.record_name, + employee_id=work_record.employee_id, + date=work_record.date, + ).exists(): + new_work_record = WorkRecords( + id=work_record.id, + record_name=work_record.record_name, + work_record_type=work_record.work_record_type, + employee_id=work_record.employee_id, + date=work_record.date, + at_work=work_record.at_work, + min_hour=work_record.min_hour, + at_work_second=work_record.at_work_second, + min_hour_second=work_record.min_hour_second, + note=work_record.note, + message=work_record.message, + is_attendance_record=work_record.is_attendance_record, + is_leave_record=work_record.is_leave_record, + day_percentage=work_record.day_percentage, + last_update=work_record.last_update, + ) + new_work_record.save() + + new_work_records = WorkRecords.objects.all() +except Exception as e: + print(f"An error occurred: {e}") diff --git a/base/models.py b/base/models.py index e4ae7b048..a2ac1ec23 100644 --- a/base/models.py +++ b/base/models.py @@ -4,22 +4,45 @@ models.py This module is used to register django models """ +from datetime import date, datetime, timedelta from typing import Iterable import django +from django.apps import apps from django.contrib import messages from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from base.horilla_company_manager import HorillaCompanyManager from horilla import horilla_middlewares from horilla.horilla_middlewares import _thread_locals +from horilla.methods import get_horilla_model_class from horilla.models import HorillaModel from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog # Create your models here. +WEEKS = [ + ("0", _("First Week")), + ("1", _("Second Week")), + ("2", _("Third Week")), + ("3", _("Fourth Week")), + ("4", _("Fifth Week")), +] + + +WEEK_DAYS = [ + ("0", _("Monday")), + ("1", _("Tuesday")), + ("2", _("Wednesday")), + ("3", _("Thursday")), + ("4", _("Friday")), + ("5", _("Saturday")), + ("6", _("Sunday")), +] def validate_time_format(value): @@ -491,14 +514,15 @@ class EmployeeShift(HorillaModel): max_length=6, default="200:00", validators=[validate_time_format] ) company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company")) - grace_time_id = models.ForeignKey( - "attendance.GraceTime", - null=True, - blank=True, - related_name="employee_shift", - on_delete=models.PROTECT, - verbose_name=_("Grace Time"), - ) + if apps.is_installed("attendance"): + grace_time_id = models.ForeignKey( + "attendance.GraceTime", + null=True, + blank=True, + related_name="employee_shift", + on_delete=models.PROTECT, + verbose_name=_("Grace Time"), + ) objects = HorillaCompanyManager("employee_shift__company_id") @@ -1092,6 +1116,21 @@ class Tags(HorillaModel): return self.title +class HorillaMailTemplate(HorillaModel): + title = models.CharField(max_length=25, unique=True) + body = models.TextField() + company_id = models.ForeignKey( + Company, + null=True, + blank=True, + on_delete=models.CASCADE, + verbose_name=_("Company"), + ) + + def __str__(self) -> str: + return f"{self.title}" + + class DynamicEmailConfiguration(HorillaModel): """ SingletonModel to keep the mail server configurations @@ -1545,3 +1584,151 @@ class TrackLateComeEarlyOut(HorillaModel): def __str__(self): tracking = _("enabled") if self.is_enable else _("disabled") return f"Tracking late come early out {tracking}" + + +class Holidays(HorillaModel): + name = models.CharField(max_length=30, null=False, verbose_name=_("Name")) + start_date = models.DateField(verbose_name=_("Start Date")) + end_date = models.DateField(null=True, blank=True, verbose_name=_("End Date")) + recurring = models.BooleanField(default=False, verbose_name=_("Recurring")) + company_id = models.ForeignKey( + Company, null=True, editable=False, on_delete=models.PROTECT + ) + objects = HorillaCompanyManager(related_company_field="company_id") + + def __str__(self): + return self.name + + +class CompanyLeaves(HorillaModel): + based_on_week = models.CharField( + max_length=100, choices=WEEKS, blank=True, null=True + ) + based_on_week_day = models.CharField(max_length=100, choices=WEEK_DAYS) + company_id = models.ForeignKey( + Company, null=True, editable=False, on_delete=models.PROTECT + ) + objects = HorillaCompanyManager(related_company_field="company_id") + + class Meta: + unique_together = ("based_on_week", "based_on_week_day") + + def __str__(self): + return f"{dict(WEEK_DAYS).get(self.based_on_week_day)} | {dict(WEEKS).get(self.based_on_week)}" + + +class PenaltyAccounts(HorillaModel): + """ + LateComeEarlyOutPenaltyAccount + """ + + employee_id = models.ForeignKey( + "employee.Employee", + on_delete=models.PROTECT, + related_name="penalty_accounts", + editable=False, + verbose_name="Employee", + null=True, + ) + if apps.is_installed("attendance"): + late_early_id = models.ForeignKey( + "attendance.AttendanceLateComeEarlyOut", + on_delete=models.CASCADE, + null=True, + editable=False, + ) + if apps.is_installed("leave"): + leave_request_id = models.ForeignKey( + "leave.LeaveRequest", null=True, on_delete=models.CASCADE, editable=False + ) + leave_type_id = models.ForeignKey( + "leave.LeaveType", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + verbose_name="Leave type", + ) + minus_leaves = models.FloatField(default=0.0, null=True) + deduct_from_carry_forward = models.BooleanField(default=False) + penalty_amount = models.FloatField(default=0.0, null=True) + + def clean(self) -> None: + super().clean() + if apps.is_installed("leave") and not self.leave_type_id and self.minus_leaves: + raise ValidationError( + {"leave_type_id": _("Specify the leave type to deduct the leave.")} + ) + if apps.is_installed("leave") and self.leave_type_id and not self.minus_leaves: + raise ValidationError( + { + "minus_leaves": _( + "If a leave type is chosen for a penalty, minus leaves are required." + ) + } + ) + if not self.minus_leaves and not self.penalty_amount: + raise ValidationError( + { + "leave_type_id": _( + "Either minus leaves or a penalty amount is required" + ) + } + ) + + if ( + self.minus_leaves or self.deduct_from_carry_forward + ) and not self.leave_type_id: + raise ValidationError({"leave_type_id": _("Leave type is required")}) + return + + class Meta: + ordering = ["-created_at"] + + +@receiver(post_save, sender=PenaltyAccounts) +def create_deduction_cutleave_from_penalty(sender, instance, created, **kwargs): + """ + This is post save method, used to create deduction and cut availabl leave days""" + # only work when creating + if created: + penalty_amount = instance.penalty_amount + if apps.is_installed("payroll") and penalty_amount: + Deduction = get_horilla_model_class(app_label="payroll", model="Deduction") + penalty = Deduction() + if instance.late_early_id: + penalty.title = f"{instance.late_early_id.get_type_display()} penalty" + penalty.one_time_date = ( + instance.late_early_id.attendance_id.attendance_date + ) + elif instance.leave_request_id: + penalty.title = f"Leave penalty {instance.leave_request_id.end_date}" + penalty.one_time_date = instance.leave_request_id.end_date + else: + penalty.title = f"Penalty on {datetime.today()}" + penalty.one_time_date = datetime.today() + penalty.include_active_employees = False + penalty.is_fixed = True + penalty.amount = instance.penalty_amount + penalty.only_show_under_employee = True + penalty.save() + penalty.include_active_employees = False + penalty.specific_employees.add(instance.employee_id) + penalty.save() + + if ( + apps.is_installed("leave") + and instance.leave_type_id + and instance.minus_leaves + ): + available = instance.employee_id.available_leave.filter( + leave_type_id=instance.leave_type_id + ).first() + unit = round(instance.minus_leaves * 2) / 2 + if not instance.deduct_from_carry_forward: + available.available_days = max(0, (available.available_days - unit)) + else: + available.carryforward_days = max( + 0, (available.carryforward_days - unit) + ) + + available.save() diff --git a/base/request_and_approve.py b/base/request_and_approve.py index 0a2dc86cb..6202d8fec 100644 --- a/base/request_and_approve.py +++ b/base/request_and_approve.py @@ -5,20 +5,15 @@ This module is used to map url patterns with request and approve methods in Dash """ import json -from datetime import date +from django.apps import apps from django.core.paginator import Paginator from django.db.models import Q from django.shortcuts import render -from asset.models import AssetRequest -from attendance.models import Attendance, AttendanceValidationCondition -from attendance.views.views import strtime_seconds from base.methods import filtersubordinates from base.models import ShiftRequest, WorkTypeRequest from horilla.decorators import login_required -from leave.models import LeaveAllocationRequest, LeaveRequest -from pms.models import Feedback def paginator_qry(qryset, page_number): @@ -62,149 +57,3 @@ def dashboard_work_type_request(request): "requests_ids": requests_ids, }, ) - - -@login_required -def dashboard_overtime_approve(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - condition = AttendanceValidationCondition.objects.first() - min_ot = strtime_seconds("00:00") - if condition is not None and condition.minimum_overtime_to_approve is not None: - min_ot = strtime_seconds(condition.minimum_overtime_to_approve) - ot_attendances = Attendance.objects.filter( - overtime_second__gte=min_ot, - attendance_validated=True, - employee_id__is_active=True, - attendance_overtime_approve=False, - ) - ot_attendances = filtersubordinates( - request, ot_attendances, "attendance.change_attendance" - ) - ot_attendances = paginator_qry(ot_attendances, page_number) - ot_attendances_ids = json.dumps([instance.id for instance in ot_attendances]) - return render( - request, - "request_and_approve/overtime_approve.html", - { - "overtime_attendances": ot_attendances, - "ot_attendances_ids": ot_attendances_ids, - "pd": previous_data, - }, - ) - - -@login_required -def dashboard_attendance_validate(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - validate_attendances = Attendance.objects.filter( - attendance_validated=False, employee_id__is_active=True - ) - validate_attendances = filtersubordinates( - request, validate_attendances, "attendance.change_attendance" - ) - validate_attendances = paginator_qry(validate_attendances, page_number) - validate_attendances_ids = json.dumps( - [instance.id for instance in validate_attendances] - ) - return render( - request, - "request_and_approve/attendance_validate.html", - { - "validate_attendances": validate_attendances, - "validate_attendances_ids": validate_attendances_ids, - "pd": previous_data, - }, - ) - - -@login_required -def leave_request_and_approve(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - leave_requests = LeaveRequest.objects.filter( - status="requested", employee_id__is_active=True, start_date__gte=date.today() - ) - leave_requests = filtersubordinates( - request, leave_requests, "leave.change_leaverequest" - ) - leave_requests = paginator_qry(leave_requests, page_number) - leave_requests_ids = json.dumps([instance.id for instance in leave_requests]) - return render( - request, - "request_and_approve/leave_request_approve.html", - { - "leave_requests": leave_requests, - "requests_ids": leave_requests_ids, - "pd": previous_data, - # "current_date":date.today(), - }, - ) - - -@login_required -def leave_allocation_approve(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - allocation_reqests = LeaveAllocationRequest.objects.filter( - status="requested", employee_id__is_active=True - ) - allocation_reqests = filtersubordinates( - request, allocation_reqests, "leave.view_leaveallocationrequest" - ) - # allocation_reqests = paginator_qry(allocation_reqests, page_number) - allocation_reqests_ids = json.dumps( - [instance.id for instance in allocation_reqests] - ) - return render( - request, - "request_and_approve/leave_allocation_approve.html", - { - "allocation_reqests": allocation_reqests, - "reqests_ids": allocation_reqests_ids, - "pd": previous_data, - # "current_date":date.today(), - }, - ) - - -@login_required -def dashboard_feedback_answer(request): - employee = request.user.employee_get - feedback_requested = Feedback.objects.filter( - Q(manager_id=employee, manager_id__is_active=True) - | Q(colleague_id=employee, colleague_id__is_active=True) - | Q(subordinate_id=employee, subordinate_id__is_active=True) - ).distinct() - feedbacks = feedback_requested.exclude(feedback_answer__employee_id=employee) - - return render( - request, - "request_and_approve/feedback_answer.html", - {"feedbacks": feedbacks, "current_date": date.today()}, - ) - - -@login_required -def dashboard_asset_request_approve(request): - asset_requests = AssetRequest.objects.filter( - asset_request_status="Requested", requested_employee_id__is_active=True - ) - - asset_requests = filtersubordinates( - request, - asset_requests, - "asset.change_assetrequest", - field="requested_employee_id", - ) - requests_ids = json.dumps([instance.id for instance in asset_requests]) - - return render( - request, - "request_and_approve/asset_requests_approve.html", - { - "asset_requests": asset_requests, - "requests_ids": requests_ids, - }, - ) diff --git a/base/scheduler.py b/base/scheduler.py index 75bbf4522..6d0d91901 100644 --- a/base/scheduler.py +++ b/base/scheduler.py @@ -1,4 +1,5 @@ import calendar +import datetime as dt from datetime import date, datetime, timedelta from apscheduler.schedulers.background import BackgroundScheduler @@ -406,6 +407,28 @@ def undo_work_type(): return +def recurring_holiday(): + from .models import Holidays + + recurring_holidays = Holidays.objects.filter(recurring=True) + today = datetime.now() + # Looping through all recurring holiday + for recurring_holiday in recurring_holidays: + start_date = recurring_holiday.start_date + end_date = recurring_holiday.end_date + new_start_date = dt.date(start_date.year + 1, start_date.month, start_date.day) + new_end_date = dt.date(end_date.year + 1, end_date.month, end_date.day) + # Checking that end date is not none + if end_date is None: + # checking if that start date is day before today + if start_date == (today - timedelta(days=1)).date(): + recurring_holiday.start_date = new_start_date + elif end_date == (today - timedelta(days=1)).date(): + recurring_holiday.start_date = new_start_date + recurring_holiday.end_date = new_end_date + recurring_holiday.save() + + scheduler = BackgroundScheduler() # Set the initial start time to the current time @@ -467,5 +490,5 @@ try: except: pass - +scheduler.add_job(recurring_holiday, "interval", hours=4) scheduler.start() diff --git a/base/static/holiday/action.js b/base/static/holiday/action.js new file mode 100644 index 000000000..ddbbdd9f6 --- /dev/null +++ b/base/static/holiday/action.js @@ -0,0 +1,384 @@ +var rowMessages = { + ar: " تم الاختيار", + de: " Ausgewählt", + es: " Seleccionado", + en: " Selected", + fr: " Sélectionné", +}; + +var excelMessages = { + ar: "هل ترغب في تنزيل ملف Excel؟", + de: "Möchten Sie die Excel-Datei herunterladen?", + es: "¿Desea descargar el archivo de Excel?", + en: "Do you want to download the excel file?", + fr: "Voulez-vous télécharger le fichier Excel?", +}; + +var deleteHolidayMessages = { + ar: "هل تريد حقًا حذف جميع العطل المحددة؟", + de: "Möchten Sie wirklich alle ausgewählten Feiertage löschen?", + es: "¿Realmente quieres eliminar todas las vacaciones seleccionadas?", + en: "Do you really want to delete all the selected holidays?", + fr: "Voulez-vous vraiment supprimer toutes les vacances sélectionnées?", +}; + +var no_rows_deleteMessages = { + ar: "لم تتم تحديد صفوف لحذف العطلات.", + de: "Es wurden keine Zeilen zum Löschen von Feiertagen ausgewählt.", + es: "No se han seleccionado filas para eliminar las vacaciones.", + en: "No rows are selected for deleting holidays.", + fr: "Aucune ligne n'a été sélectionnée pour supprimer les vacances.", +}; +var downloadMessages = { + ar: "هل ترغب في تنزيل القالب؟", + de: "Möchten Sie die Vorlage herunterladen?", + es: "¿Quieres descargar la plantilla?", + en: "Do you want to download the template?", + fr: "Voulez-vous télécharger le modèle ?", +}; +function makeListUnique(list) { + return Array.from(new Set(list)); +} + +function createHolidayHxValue() { + var pd = $(".oh-pagination").attr("data-pd"); + var hxValue = JSON.stringify(pd); + $("#holidayCreateButton").attr("hx-vals", `{"pd":${hxValue}}`); +} + +tickHolidayCheckboxes(); +function makeHolidayListUnique(list) { + return Array.from(new Set(list)); +} + +function getCurrentLanguageCode(callback) { + var languageCode = $("#main-section-data").attr("data-lang"); + var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"]; + if (allowedLanguageCodes.includes(languageCode)) { + callback(languageCode); + } else { + $.ajax({ + type: "GET", + url: "/employee/get-language-code/", + success: function (response) { + var ajaxLanguageCode = response.language_code; + $("#main-section-data").attr("data-lang", ajaxLanguageCode); + callback( + allowedLanguageCodes.includes(ajaxLanguageCode) + ? ajaxLanguageCode + : "en" + ); + }, + error: function () { + callback("en"); + }, + }); + } +} + +function tickHolidayCheckboxes() { + var ids = JSON.parse($("#selectedHolidays").attr("data-ids") || "[]"); + uniqueIds = makeHolidayListUnique(ids); + toggleHighlight(uniqueIds); + click = $("#selectedHolidays").attr("data-clicked"); + if (click === "1") { + $(".all-holidays").prop("checked", true); + } + uniqueIds.forEach(function (id) { + $("#" + id).prop("checked", true); + }); + var selectedCount = uniqueIds.length; + getCurrentLanguageCode(function (code) { + languageCode = code; + var message = rowMessages[languageCode]; + if (selectedCount > 0) { + $("#unselectAllHolidays").css("display", "inline-flex"); + $("#exportHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").text(selectedCount + " -" + message); + } else { + $("#unselectAllHolidays").css("display", "none "); + $("#selectedShowHolidays").css("display", "none"); + $("#exportHolidays").css("display", "none"); + } + }); +} + +function addingHolidayIds() { + var ids = JSON.parse($("#selectedHolidays").attr("data-ids") || "[]"); + var selectedCount = 0; + + $(".all-holidays-row").each(function () { + if ($(this).is(":checked")) { + ids.push(this.id); + } else { + var index = ids.indexOf(this.id); + if (index > -1) { + ids.splice(index, 1); + $(".all-holidays").prop("checked", false); + } + } + }); + + ids = makeHolidayListUnique(ids); + toggleHighlight(ids); + selectedCount = ids.length; + + getCurrentLanguageCode(function (code) { + languageCode = code; + var message = rowMessages[languageCode]; + $("#selectedHolidays").attr("data-ids", JSON.stringify(ids)); + if (selectedCount === 0) { + $("#selectedShowHolidays").css("display", "none"); + $("#exportHolidays").css("display", "none"); + $('#unselectAllHolidays').css("display", "none"); + } else { + $("#unselectAllHolidays").css("display", "inline-flex"); + $("#exportHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").text(selectedCount + " - " + message); + } + }); +} + +function selectAllHolidays() { + $("#selectedHolidays").attr("data-clicked", 1); + $("#selectedShowHolidays").removeAttr("style"); + var savedFilters = JSON.parse(localStorage.getItem("savedFilters")); + + if (savedFilters && savedFilters["filterData"] !== null) { + var filter = savedFilters["filterData"]; + $.ajax({ + url: "/holiday-select-filter", + data: { page: "all", filter: JSON.stringify(filter) }, + type: "GET", + dataType: "json", + success: function (response) { + var employeeIds = response.employee_ids; + + if (Array.isArray(employeeIds)) { + // Continue + } else { + console.error("employee_ids is not an array:", employeeIds); + } + + for (var i = 0; i < employeeIds.length; i++) { + var empId = employeeIds[i]; + $("#" + empId).prop("checked", true); + } + $("#selectedHolidays").attr("data-ids", JSON.stringify(employeeIds)); + + count = makeHolidayListUnique(employeeIds); + tickHolidayCheckboxes(count); + }, + error: function (xhr, status, error) { + console.error("Error:", error); + }, + }); + } else { + $.ajax({ + url: "/holiday-select", + data: { page: "all" }, + type: "GET", + dataType: "json", + success: function (response) { + var employeeIds = response.employee_ids; + if (Array.isArray(employeeIds)) { + // Continue + } else { + console.error("employee_ids is not an array:", employeeIds); + } + + for (var i = 0; i < employeeIds.length; i++) { + var empId = employeeIds[i]; + $("#" + empId).prop("checked", true); + } + var previousIds = $("#selectedHolidays").attr("data-ids"); + $("#selectedHolidays").attr( + "data-ids", + JSON.stringify( + Array.from(new Set([...employeeIds, ...JSON.parse(previousIds)])) + ) + ); + count = makeHolidayListUnique(employeeIds); + tickHolidayCheckboxes(count); + }, + error: function (xhr, status, error) { + console.error("Error:", error); + }, + }); + } +} + +function unselectAllHolidays() { + $("#selectedHolidays").attr("data-clicked", 0); + $.ajax({ + url: "/holiday-select", + data: { page: "all", filter: "{}" }, + type: "GET", + dataType: "json", + success: function (response) { + var employeeIds = response.employee_ids; + + if (Array.isArray(employeeIds)) { + // Continue + } else { + console.error("employee_ids is not an array:", employeeIds); + } + + for (var i = 0; i < employeeIds.length; i++) { + var empId = employeeIds[i]; + $("#" + empId).prop("checked", false); + $(".all-holidays").prop("checked", false); + } + var ids = JSON.parse($("#selectedHolidays").attr("data-ids") || "[]"); + var uniqueIds = makeListUnique(ids); + toggleHighlight(uniqueIds); + $("#selectedHolidays").attr("data-ids", JSON.stringify([])); + + count = []; + tickHolidayCheckboxes(count); + }, + error: function (xhr, status, error) { + console.error("Error:", error); + }, + }); +} + +function exportHolidays() { + var currentDate = new Date().toISOString().slice(0, 10); + var language_code = null; + getCurrentLanguageCode(function (code) { + language_code = code; + var confirmMessage = excelMessages[language_code]; + ids = []; + ids = JSON.parse($("#selectedHolidays").attr("data-ids")); + Swal.fire({ + text: confirmMessage, + icon: "question", + showCancelButton: true, + confirmButtonColor: "#008000", + cancelButtonColor: "#d33", + confirmButtonText: "Confirm", + }).then(function (result) { + if (result.isConfirmed) { + $.ajax({ + type: "GET", + url: "/holiday-info-export", + data: { + ids: JSON.stringify(ids), + }, + dataType: "binary", + xhrFields: { + responseType: "blob", + }, + success: function (response) { + const file = new Blob([response], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + const url = URL.createObjectURL(file); + const link = document.createElement("a"); + link.href = url; + link.download = "holiday_leaves" + currentDate + ".xlsx"; + document.body.appendChild(link); + link.click(); + }, + error: function (xhr, textStatus, errorThrown) { + console.error("Error downloading file:", errorThrown); + }, + }); + } + }); + }); +} + +$("#bulkHolidaysDelete").click(function (e) { + e.preventDefault(); + var languageCode = null; + getCurrentLanguageCode(function (code) { + languageCode = code; + var confirmMessage = deleteHolidayMessages[languageCode]; + var textMessage = no_rows_deleteMessages[languageCode]; + ids = []; + ids.push($("#selectedHolidays").attr("data-ids")); + ids = JSON.parse($("#selectedHolidays").attr("data-ids")); + if (ids.length === 0) { + Swal.fire({ + text: textMessage, + icon: "warning", + confirmButtonText: "Close", + }); + } else { + Swal.fire({ + text: confirmMessage, + icon: "error", + showCancelButton: true, + confirmButtonColor: "#008000", + cancelButtonColor: "#d33", + confirmButtonText: "Confirm", + }).then(function (result) { + if (result.isConfirmed) { + ids = []; + ids.push($("#selectedHolidays").attr("data-ids")); + ids = JSON.parse($("#selectedHolidays").attr("data-ids")); + $.ajax({ + type: "POST", + url: "/holidays-bulk-delete", + data: { + csrfmiddlewaretoken: getCookie("csrftoken"), + ids: JSON.stringify(ids), + }, + success: function (response, textStatus, jqXHR) { + if (jqXHR.status === 200) { + location.reload(); + } else { + } + }, + }); + } + }); + } + }); +}); + +$("#holidaysInfoImport").click(function (e) { + e.preventDefault(); + var languageCode = null; + getCurrentLanguageCode(function (code) { + languageCode = code; + var confirmMessage = downloadMessages[languageCode]; + Swal.fire({ + text: confirmMessage, + icon: "question", + showCancelButton: true, + confirmButtonColor: "#008000", + cancelButtonColor: "#d33", + confirmButtonText: "Confirm", + }).then(function (result) { + if (result.isConfirmed) { + $.ajax({ + type: "GET", + url: "holidays-excel-template", + dataType: "binary", + xhrFields: { + responseType: "blob", + }, + success: function (response) { + const file = new Blob([response], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + const url = URL.createObjectURL(file); + const link = document.createElement("a"); + link.href = url; + link.download = "holiday_excel.xlsx"; + document.body.appendChild(link); + link.click(); + }, + error: function (xhr, textStatus, errorThrown) { + console.error("Error downloading file:", errorThrown); + }, + }); + } + }); + }); +}); diff --git a/base/templates/announcement/announcement_one.html b/base/templates/announcement/announcement_one.html index c3e587f14..aa64f1e62 100644 --- a/base/templates/announcement/announcement_one.html +++ b/base/templates/announcement/announcement_one.html @@ -60,10 +60,8 @@ {% endif %} - {% trans "Posted on" %}  {{ anoun.created_at|date:"F j, Y" - }}   - {% trans "at" %}   {{ anoun.created_at|time:"g:i A" - }} + {% trans "Posted on" %}  {{ anoun.created_at|date:"F j, Y"}}   + {% trans "at" %}   {{ anoun.created_at|time:"g:i A"}}
diff --git a/base/templates/announcement/comment_view.html b/base/templates/announcement/comment_view.html index f13f4c2d7..eb42a2caa 100644 --- a/base/templates/announcement/comment_view.html +++ b/base/templates/announcement/comment_view.html @@ -1,4 +1,4 @@ -{% load basefilters %} +{% load basefilters static %} {% load i18n %} @@ -54,7 +54,7 @@ >
diff --git a/base/templates/base/general_settings.html b/base/templates/base/general_settings.html index 486d330c5..1a5b52dbb 100644 --- a/base/templates/base/general_settings.html +++ b/base/templates/base/general_settings.html @@ -1,18 +1,18 @@ -{% extends 'settings.html' %} {% block settings %}{% load i18n %} +{% extends 'settings.html' %} {% block settings %}{% load i18n %} {% load horillafilters %} -{% if perms.recruitment.change_recruitmentgeneralsetting %} +{% if "recruitment"|app_installed and perms.recruitment.change_recruitmentgeneralsetting %} {% include "recruitment/settings/settings.html" %} {% endif %} -{% if perms.offboarding.change_offboardinggeneralsetting %} +{% if "offboarding"|app_installed and perms.offboarding.change_offboardinggeneralsetting %} {% include "offboarding/settings/settings.html" %} {% endif %} -{% if perms.attendance.change_attendancegeneralsetting %} +{% if "attendance"|app_installed and perms.attendance.change_attendancegeneralsetting %} {% include "attendance/settings/settings.html" %} {% endif %} -{% if perms.payroll.change_payrollgeneralsetting %} +{% if "payroll"|app_installed and perms.payroll.change_payrollgeneralsetting %} {% include "payroll/settings/settings.html" %} {% endif %} @@ -24,8 +24,10 @@ {% include "announcement/expiry_day.html" %} {% endif %} -{% if perms.payroll.change_encashmentgeneralsetting %} - {% include "settings/encashment_settings.html" %} +{% if "payroll"|app_installed %} + {% if perms.payroll.change_encashmentgeneralsetting %} + {% include "settings/encashment_settings.html" %} + {% endif %} {% endif %} {% if perms.base.view_historytrackingfields %} @@ -36,7 +38,7 @@ {% include "base/audit_tag/employee_account_block_unblock.html" %} {% endif %} -{% if perms.payroll.view_payrollsettings %} +{% if "payroll"|app_installed and perms.payroll.view_payrollsettings %} {% include "payroll/settings/payroll_settings.html" %} {% endif %} diff --git a/base/templates/base/rotating_shift/htmx/group_by.html b/base/templates/base/rotating_shift/htmx/group_by.html index 4e61cf592..a55353a9c 100644 --- a/base/templates/base/rotating_shift/htmx/group_by.html +++ b/base/templates/base/rotating_shift/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} {% if messages %}
diff --git a/base/templates/base/rotating_work_type/htmx/group_by.html b/base/templates/base/rotating_work_type/htmx/group_by.html index dd8c15791..42fa830d9 100644 --- a/base/templates/base/rotating_work_type/htmx/group_by.html +++ b/base/templates/base/rotating_work_type/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} {% if messages %}
diff --git a/base/templates/common_form.html b/base/templates/common_form.html new file mode 100644 index 000000000..d01a7db98 --- /dev/null +++ b/base/templates/common_form.html @@ -0,0 +1,49 @@ +{% load widget_tweaks %} +{% load i18n %} + +
+ {% if form.verbose_name %} +
+
+
{{ form.verbose_name }}
+
+
+ {% endif %} +
+
+
{{ form.non_field_errors }}
+ {% for field in form.visible_fields %} +
+
+ + {% if field.help_text != '' %} + + {% endif %} +
+ + {% if field.field.widget.input_type == 'checkbox' %} +
{{ field|add_class:'oh-switch__checkbox' }}
+ {% else %} + {{ field|add_class:'form-control' }} + {% endif %} + {{ field.errors }} +
+ {% endfor %} +
+ + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + +
+ +
+
+
+ diff --git a/base/templates/company_leave/company_leave.html b/base/templates/company_leave/company_leave.html new file mode 100644 index 000000000..725c53632 --- /dev/null +++ b/base/templates/company_leave/company_leave.html @@ -0,0 +1,127 @@ +{% load i18n %} {% load static %} +{% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+{% endif %} +{% include 'filter_tags.html' %} +{% if company_leaves %} +
+ + + +{% else %} +
+
+ +

{% trans "There are no company leaves at the moment." %}

+
+
+{% endif %} diff --git a/base/templates/company_leave/company_leave_creation_form.html b/base/templates/company_leave/company_leave_creation_form.html new file mode 100644 index 000000000..512922714 --- /dev/null +++ b/base/templates/company_leave/company_leave_creation_form.html @@ -0,0 +1,56 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} {% if form.errors %} +
+
+ {% for error in form.non_field_errors %} +
{{ error }}
+ {% endfor %} +
+
+{% endif %} +
+ {% trans "Create Company Leaves" %} + +
+
+
+ + {{form.based_on_week}} {{form.based_on_week.errors}} + + {{form.based_on_week_day}} {{form.based_on_week_day.errors}} + +
+
diff --git a/base/templates/company_leave/company_leave_update_form.html b/base/templates/company_leave/company_leave_update_form.html new file mode 100644 index 000000000..5643d56b4 --- /dev/null +++ b/base/templates/company_leave/company_leave_update_form.html @@ -0,0 +1,58 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} +{% if form.errors %} + +
+
+ {% for error in form.non_field_errors %} +
{{ error }}
+ {% endfor %} +
+
+{% endif %} +
+ {% trans "Update Company Leaves" %} + +
+
+
+ + {{form.based_on_week}} + + {{form.based_on_week_day}} + +
+
diff --git a/base/templates/company_leave/company_leave_view.html b/base/templates/company_leave/company_leave_view.html new file mode 100644 index 000000000..bf5495aa5 --- /dev/null +++ b/base/templates/company_leave/company_leave_view.html @@ -0,0 +1,100 @@ +{% extends 'index.html' %} +{% block content %} +{% load static %} +{% load i18n %} + +
+
+

{% trans "Company Leaves" %}

+ + + +
+
+
+ {% if company_leaves %} +
+ + +
+
+
+ + + +
+ {% endif %} + {% if perms.base.add_companyleave %} +
+
+ +
+
+ {% endif %} + +
+
+
+
+ +
+ {% include 'company_leave/company_leave.html' %} +
+ + + + + + +{% endblock %} diff --git a/base/templates/holiday/holiday.html b/base/templates/holiday/holiday.html new file mode 100644 index 000000000..35e60dba1 --- /dev/null +++ b/base/templates/holiday/holiday.html @@ -0,0 +1,262 @@ +{% load i18n %} {% load static %} {% include 'filter_tags.html' %} +{% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+{% endif %} {% if holidays %} +
+ {% trans "Select All Holidays" %} +
+
+ {% trans "Unselect All Holidays" %} +
+ + + +
+
+
+
+
+
+ +
+
+
+ {% trans "Holiday Name" %} +
+
+ {% trans "Start Date" %} +
+
+ {% trans "End Date" %} +
+
{% trans "Recurring" %}
+ {% if perms.base.change_holiday or perms.base.delete_holiday %} +
{% trans "Actions" %}
+ {% endif %} +
+
+
+ {% for holiday in holidays %} +
+
+
+ +
+
+
{{holiday.name}}
+
+ {{holiday.start_date}} +
+
+ {{holiday.end_date}} +
+
+ {% if holiday.recurring %} + {% trans "Yes" %} + {% else %} + {% trans "No"%} + {% endif %} +
+ {% if perms.base.change_holiday or perms.base.delete_holiday %} +
+
+ {% if perms.base.change_holiday %} + + {% endif %} {% if perms.base.delete_holiday %} + + + + {% endif %} +
+
+ {% endif %} +
+ {% endfor %} +
+
+
+ +
+ + {% trans "Page" %} {{ holidays.number }} + {% trans "of" %} {{ holidays.paginator.num_pages }}. + + +
+{% else %} + +
+
+ +

+ {% trans "There are no holidays at the moments." %} +

+
+
+ +{% endif %} + + + diff --git a/base/templates/holiday/holiday_export_filter_form.html b/base/templates/holiday/holiday_export_filter_form.html new file mode 100644 index 000000000..9b2af929e --- /dev/null +++ b/base/templates/holiday/holiday_export_filter_form.html @@ -0,0 +1,78 @@ +{% load i18n %} +
+

+ {% trans "Export Holidays" %} +

+ +
+
+
+ {% csrf_token %} +
+
+
{% trans "Excel columns" %}
+
+
+ {% for field in export_column.selected_fields %} +
+
+ +
+
+ {% endfor %} +
+
+
+
+
{% trans "Holiday" %}
+
+
+
+
+ + {{export_filter.form.from_date}} +
+
+
+
+ + {{export_filter.form.to_date}} +
+
+
+
+ + {{export_filter.form.recurring}} +
+
+
+
+
+
+ +
+
diff --git a/base/templates/holiday/holiday_filter.html b/base/templates/holiday/holiday_filter.html new file mode 100644 index 000000000..bf906b124 --- /dev/null +++ b/base/templates/holiday/holiday_filter.html @@ -0,0 +1,45 @@ +{% load i18n %} +
+ +
diff --git a/base/templates/holiday/holiday_form.html b/base/templates/holiday/holiday_form.html new file mode 100644 index 000000000..73b238b0e --- /dev/null +++ b/base/templates/holiday/holiday_form.html @@ -0,0 +1,70 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} +
+ {% trans "Create Holiday" %} + +
+
+
+ + {{form.name}} {{form.name.errors}} +
+
+
+ + {{form.start_date }} {{form.start_date.errors }} +
+
+
+
+ + {{form.end_date }} {{form.end_date.errors}} +
+
+
+ +
{{form.recurring}} {{form.recurring.errors}}
+ +
+
diff --git a/base/templates/holiday/holiday_update_form.html b/base/templates/holiday/holiday_update_form.html new file mode 100644 index 000000000..020c20dab --- /dev/null +++ b/base/templates/holiday/holiday_update_form.html @@ -0,0 +1,63 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} +
+ {% trans "Update Holiday" %} + +
+
+
+ + {{form.name}} {{form.name.errors}} + + + {{form.start_date}} {{form.start_date.errors}} + + + {{form.end_date}} {{form.end_date.errors}} + + +
{{form.recurring}} {{form.recurring.errors}}
+ +
+
diff --git a/base/templates/holiday/holiday_view.html b/base/templates/holiday/holiday_view.html new file mode 100644 index 000000000..1ffc336d2 --- /dev/null +++ b/base/templates/holiday/holiday_view.html @@ -0,0 +1,297 @@ +{% extends 'index.html' %} {% block content %} {% load static %} {% load i18n %} + +
+ +
+

{% trans "Holidays" %}

+ + + +
+ + +
+ {% if holidays %} + +
+ + +
+ + + +
+ +
+ + {% include "holiday/holiday_filter.html" %} +
+ + {% endif %} + + + {% if perms.base.add_holiday or perms.base.delete_holiday%} +
+ + +
+ {% endif %} + + + + {% if perms.base.add_holiday %} +
+
+ +
+
+ {% endif %} + +
+ +
+
+ + + + + +
+ {% if holidays %} + {% include 'holiday/holiday.html' %} + {% else %} + +
+
+ +

+ {% trans "There are no holidays at the moment." %} +

+
+
+ + {% endif %} +
+ + + + + + + + + + + + + + + + +{% endblock %} diff --git a/base/templates/horilla_form.html b/base/templates/horilla_form.html new file mode 100644 index 000000000..4d131f2ab --- /dev/null +++ b/base/templates/horilla_form.html @@ -0,0 +1,56 @@ +{% load i18n %}{% load widget_tweaks %} {% load horillafilters %} +{% for field in form %} + {% if field.field.widget.is_hidden %} + {{ field }} + {% endif %} +{% endfor %} +
+
+
+
{{form.non_field_errors}}
+ {% for field in form.visible_fields %} + {% if field.field.widget|is_select_multiple or field.field.widget|is_text_area %} + +
+ + {{ field|add_class:"form-control" }} +
+ {{field.errors}} + {% else %} +
+ + {% if field.field.widget.input_type == "checkbox" %} +
+ {{ field|add_class:"oh-switch__checkbox" }} + +
+ {% else %} + {{ field|add_class:"form-control" }} + {% endif %} + {{field.errors}} +
+ {% endif %} + {% endfor %} + +
+ + +
+
diff --git a/base/templates/mail/empty_mail_template.html b/base/templates/mail/empty_mail_template.html new file mode 100644 index 000000000..7f077c06a --- /dev/null +++ b/base/templates/mail/empty_mail_template.html @@ -0,0 +1,89 @@ +{% extends 'index.html' %} +{% load static %} +{% load i18n %} +{% block content %} + + +
+
+

{% trans "Mail Templates" %}

+
+ {% if perms.base.add_horillamailtemplate %} + + {% endif %} +
+
+ +
+
+ Page not found. 404. +
{% trans "There are currently no email templates." %}
+
+
+
+ + + + + +{% endblock content %} diff --git a/base/templates/mail/htmx/form.html b/base/templates/mail/htmx/form.html new file mode 100644 index 000000000..5b741f632 --- /dev/null +++ b/base/templates/mail/htmx/form.html @@ -0,0 +1,65 @@ +{% load i18n %} +{% if form.instance.id %} +
+{% else %} + +{% endif %} +
+
+
+
+
+ + {{ form.title }} +
+
+ + {{ form.body }} +
+
+ {% trans "Hint: Type '{' to get sender or receiver data" %} +
+
+ + {{ form.company_id }} +
+
+
+
+
+ +
+ diff --git a/base/templates/mail/view_templates.html b/base/templates/mail/view_templates.html new file mode 100644 index 000000000..bde8f3e8e --- /dev/null +++ b/base/templates/mail/view_templates.html @@ -0,0 +1,109 @@ +{% extends 'index.html' %} +{% load static %} +{% load i18n %} +{% block content %} + + +
+
+

{% trans "Mail Templates" %}

+
+ {% if perms.base.add_horillamailtemplate %} + + {% endif %} +
+ +
+ {% for template in templates %} +
+ {% if perms.base.delete_horillamailtemplate %} +

{{ template.title }} + +

+ {% endif %} + {% if perms.base.change_horillamailtemplate %} + + {% endif %} +
+
{{ template.body|safe }}
+
+ {% if perms.base.change_horillamailtemplate %} + {% trans "View Template" %} + {% endif %} +
+ {% endfor %} +
+ + + + +{% endblock %} diff --git a/base/templates/multi_approval_condition/condition_table.html b/base/templates/multi_approval_condition/condition_table.html index 74d80a834..8df524093 100644 --- a/base/templates/multi_approval_condition/condition_table.html +++ b/base/templates/multi_approval_condition/condition_table.html @@ -20,7 +20,7 @@
{% trans "Condition Operator" %}
{% trans "Condition Value" %}
{% trans "Approval Managers" %}
- {% if perms.leave.change_availableleave or perms.leave.delete_availableleave or request.user|is_reportingmanager %} + {% if perms.base.change_multipleapprovalcondition or perms.base.delete_multipleapprovalcondition or request.user|is_reportingmanager %}
{% trans "Actions" %}
{% endif %}
@@ -47,10 +47,10 @@ {{ forloop.counter}}. {{ manager }}
{% endfor %}
- {% if perms.leave.change_availableleave or perms.leave.delete_availableleave or request.user|is_reportingmanager %} + {% if perms.base.change_multipleapprovalcondition or perms.base.delete_multipleapprovalcondition or request.user|is_reportingmanager %}
- {% if request.user|is_reportingmanager or perms.leave.change_availableleave %} + {% if request.user|is_reportingmanager or perms.base.change_multipleapprovalcondition %} {% endif %} - {% if request.user|is_reportingmanager or perms.leave.delete_availableleave %} + {% if request.user|is_reportingmanager or perms.base.delete_multipleapprovalcondition %} diff --git a/base/templates/penalty/penalty_view.html b/base/templates/penalty/penalty_view.html new file mode 100644 index 000000000..0c741d2e1 --- /dev/null +++ b/base/templates/penalty/penalty_view.html @@ -0,0 +1,42 @@ +{% load static %} {% load i18n %} {% load horillafilters %} +{% if records %} +
+
+
+ {% if "leave"|app_installed %} +
{% trans "Leave Type" %}
+
{% trans "Minus Days" %}
+
{% trans "Deducted From" %}{% trans "CFD" %}
+ {% endif %} +
{% trans "Penalty amount" %}
+
{% trans "Created Date" %}
+
+
+
+ {% for acc in records %} +
+ {% if "leave"|app_installed %} +
{{ acc.leave_type_id }}
+
{{ acc.minus_leaves }}
+
{{acc.deduct_from_carry_forward|yes_no}}
+ {% endif %} +
+ {{currency}} {{ acc.penalty_amount }} +
+
{{ acc.created_at }}
+
+ {% endfor %} +
+
+{% else %} +
+
+ Page not found. 404. +
+ {% trans "No penalties found" %} +
+
+
+{% endif %} \ No newline at end of file diff --git a/base/templates/request_and_approve/asset_requests_approve.html b/base/templates/request_and_approve/asset_requests_approve.html index 5c864621a..bc5c4f4a4 100644 --- a/base/templates/request_and_approve/asset_requests_approve.html +++ b/base/templates/request_and_approve/asset_requests_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if asset_requests %}
@@ -80,7 +80,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/attendance_validate.html b/base/templates/request_and_approve/attendance_validate.html index 4e38f640b..921624de6 100644 --- a/base/templates/request_and_approve/attendance_validate.html +++ b/base/templates/request_and_approve/attendance_validate.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %}
@@ -78,7 +78,7 @@
diff --git a/base/templates/request_and_approve/feedback_answer.html b/base/templates/request_and_approve/feedback_answer.html index 02f995d55..1c632a5a5 100644 --- a/base/templates/request_and_approve/feedback_answer.html +++ b/base/templates/request_and_approve/feedback_answer.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if feedbacks %}
@@ -49,7 +49,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/leave_allocation_approve.html b/base/templates/request_and_approve/leave_allocation_approve.html index 6ff4e077b..be1720307 100644 --- a/base/templates/request_and_approve/leave_allocation_approve.html +++ b/base/templates/request_and_approve/leave_allocation_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if allocation_reqests %}
@@ -64,7 +64,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/leave_request_approve.html b/base/templates/request_and_approve/leave_request_approve.html index 0774c00a0..f7547382b 100644 --- a/base/templates/request_and_approve/leave_request_approve.html +++ b/base/templates/request_and_approve/leave_request_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if leave_requests %}
@@ -45,7 +45,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/overtime_approve.html b/base/templates/request_and_approve/overtime_approve.html index 3479e7488..d7cbc2d84 100644 --- a/base/templates/request_and_approve/overtime_approve.html +++ b/base/templates/request_and_approve/overtime_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %}
@@ -80,7 +80,7 @@
diff --git a/base/templates/request_and_approve/shift_request.html b/base/templates/request_and_approve/shift_request.html index ac4f1a47d..574e0821e 100644 --- a/base/templates/request_and_approve/shift_request.html +++ b/base/templates/request_and_approve/shift_request.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if requests %}
@@ -66,7 +66,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/work_type_request.html b/base/templates/request_and_approve/work_type_request.html index 7538b38e5..d73b2e299 100644 --- a/base/templates/request_and_approve/work_type_request.html +++ b/base/templates/request_and_approve/work_type_request.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if requests %}
@@ -66,7 +66,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/shift_request/htmx/allocation_details.html b/base/templates/shift_request/htmx/allocation_details.html index 958938714..06ce545a2 100644 --- a/base/templates/shift_request/htmx/allocation_details.html +++ b/base/templates/shift_request/htmx/allocation_details.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} {% load yes_no %} +{% load i18n %} {% load horillafilters %}
{% trans "There is no comments to show." %} - +
diff --git a/base/templates/shift_request/htmx/group_by.html b/base/templates/shift_request/htmx/group_by.html index fe5dc2a17..db0d81f12 100644 --- a/base/templates/shift_request/htmx/group_by.html +++ b/base/templates/shift_request/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %}
diff --git a/base/templates/shift_request/htmx/requests.html b/base/templates/shift_request/htmx/requests.html index bd4df1648..07770995a 100755 --- a/base/templates/shift_request/htmx/requests.html +++ b/base/templates/shift_request/htmx/requests.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} diff --git a/base/templates/shift_request/htmx/shift_comment.html b/base/templates/shift_request/htmx/shift_comment.html index 8d3deb4ad..cdaf89199 100644 --- a/base/templates/shift_request/htmx/shift_comment.html +++ b/base/templates/shift_request/htmx/shift_comment.html @@ -136,7 +136,7 @@ >
diff --git a/base/templates/shift_request/htmx/shift_request_detail.html b/base/templates/shift_request/htmx/shift_request_detail.html index 34655e44b..197fbb907 100644 --- a/base/templates/shift_request/htmx/shift_request_detail.html +++ b/base/templates/shift_request/htmx/shift_request_detail.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} {% load yes_no %} +{% load i18n %} {% load horillafilters %}
+ diff --git a/base/templates/shift_request/shift_request.html b/base/templates/shift_request/shift_request.html index 2113bbbee..2d34e81e4 100644 --- a/base/templates/shift_request/shift_request.html +++ b/base/templates/shift_request/shift_request.html @@ -25,4 +25,4 @@ {{form}} -
+
diff --git a/base/templates/shift_request/shift_request_export.html b/base/templates/shift_request/shift_request_export.html index 4045b59e9..12ee10a14 100644 --- a/base/templates/shift_request/shift_request_export.html +++ b/base/templates/shift_request/shift_request_export.html @@ -141,7 +141,7 @@
- diff --git a/base/templates/work_type_request/htmx/group_by.html b/base/templates/work_type_request/htmx/group_by.html index 89dae2135..b1e0a4bd1 100644 --- a/base/templates/work_type_request/htmx/group_by.html +++ b/base/templates/work_type_request/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} {% if messages %}
{% for message in messages %} diff --git a/base/templates/work_type_request/htmx/requests.html b/base/templates/work_type_request/htmx/requests.html index c11f53106..7ad41757a 100755 --- a/base/templates/work_type_request/htmx/requests.html +++ b/base/templates/work_type_request/htmx/requests.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} diff --git a/base/templates/work_type_request/htmx/work_type_comment.html b/base/templates/work_type_request/htmx/work_type_comment.html index 607f15f3f..5ed25c055 100644 --- a/base/templates/work_type_request/htmx/work_type_comment.html +++ b/base/templates/work_type_request/htmx/work_type_comment.html @@ -136,7 +136,7 @@ >
diff --git a/base/templates/work_type_request/htmx/work_type_request_single_view.html b/base/templates/work_type_request/htmx/work_type_request_single_view.html index b1c61e135..ceb4396bd 100644 --- a/base/templates/work_type_request/htmx/work_type_request_single_view.html +++ b/base/templates/work_type_request/htmx/work_type_request_single_view.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} {% load yes_no %} {% load static %} +{% load i18n %} {% load horillafilters %} {% load static %} {% if messages %}
{% for message in messages %} diff --git a/base/templates/work_type_request/request_update_form.html b/base/templates/work_type_request/request_update_form.html index b519c9bc3..700c3cc7f 100644 --- a/base/templates/work_type_request/request_update_form.html +++ b/base/templates/work_type_request/request_update_form.html @@ -30,4 +30,4 @@ function toggleFunctionWorkTypeRequestForm(){ }); }) toggleFunctionWorkTypeRequestForm(); - + diff --git a/base/templatetags/basefilters.py b/base/templatetags/basefilters.py index dece37dd2..99905c95f 100644 --- a/base/templatetags/basefilters.py +++ b/base/templatetags/basefilters.py @@ -1,6 +1,7 @@ import json from django import template +from django.apps import apps from django.core.paginator import Page, Paginator from django.template.defaultfilters import register @@ -127,18 +128,26 @@ def abs_value(value): @register.filter(name="config_perms") def config_perms(user): - permissions = [ - "leave.add_holiday", - "leave.change_holiday", - "leave.add_companyleaves", - "leave.change_companyleaves", - "leave.view_restrictleave", - "recruitment.add_recritmentmailtemplates", - "recruitment.view_recritmentmailtemplates", - ] - for perm in permissions: - if user.has_perm(perm): - return True + app_permissions = { + "leave": [ + "leave.add_holiday", + "leave.change_holiday", + "leave.add_companyleaves", + "leave.change_companyleaves", + "leave.view_restrictleave", + ], + "base": [ + "base.add_horillamailtemplates", + "base.view_horillamailtemplates", + ], + } + + for app, perms in app_permissions.items(): + if apps.is_installed(app): + for perm in perms: + if user.has_perm(perm): + return True + return False @register.filter(name="startswith") diff --git a/base/templatetags/horillafilters.py b/base/templatetags/horillafilters.py new file mode 100644 index 000000000..77397c37e --- /dev/null +++ b/base/templatetags/horillafilters.py @@ -0,0 +1,288 @@ +""" +horillafilters.py + +This module is used to write custom template filters. + +""" + +import base64 +from datetime import date, datetime, timedelta +from itertools import groupby + +from django import template +from django.apps import apps +from django.forms.widgets import SelectMultiple, Textarea +from django.template import TemplateSyntaxError +from django.template.defaultfilters import register +from django.utils.translation import gettext as _ + +from horilla.horilla_middlewares import _thread_locals +from horilla.methods import get_horilla_model_class +from base.models import EmployeeShiftSchedule +from employee.methods.duration_methods import strtime_seconds + +register = template.Library() + + +@register.filter(name="is_string") +def is_string(value): + return isinstance(value, str) + + +@register.filter(name="checkminimumot") +def checkminimumot(ot=None): + """ + This filter method is used to check minimum overtime from + the attendance validation condition + """ + if ot is not None: + if apps.is_installed("attendance"): + AttendanceValidationCondition = get_horilla_model_class( + app_label="attendance", model="attendancevalidationcondition" + ) + condition = AttendanceValidationCondition.objects.all() + else: + condition = None + if condition.exists(): + minimum_overtime_to_approve = condition[0].minimum_overtime_to_approve + overtime_second = strtime_seconds(ot) + minimum_ot_approve_seconds = strtime_seconds(minimum_overtime_to_approve) + if overtime_second > minimum_ot_approve_seconds: + return True + return False + + +@register.filter(name="checkmanager") +def checkmanager(user, employee): + """ + This filter method is used to check request user is manager of the employee + args: + user : request.user + employee : employee instance + + """ + + employee_user = user.employee_get + employee_manager = employee.employee_work_info.reporting_manager_id + return bool( + employee_user == employee_manager + or user.is_superuser + or user.has_perm("attendance.change_attendance") + ) + + +@register.filter(name="is_clocked_in") +def is_clocked_in(user): + """ + This filter method is used to check the user is clocked in or not + args: + user : request.user + """ + + try: + employee = user.employee_get + except: + return False + if apps.is_installed("attendance"): + last_attendance = ( + employee.employee_attendances.all().order_by("attendance_date", "id").last() + ) + if last_attendance is not None: + last_activity = employee.employee_attendance_activities.filter( + attendance_date=last_attendance.attendance_date + ).last() + return False if last_activity is None else last_activity.clock_out is None + return False + + +class DynamicRegroupNode(template.Node): + """ + DynamicRegroupNode + """ + + def __init__(self, target, parser, expression, var_name): + self.target = target + self.expression = template.Variable(expression) + self.var_name = var_name + self.parser = parser + + def render(self, context): + obj_list = self.target.resolve(context, True) + if obj_list is None: + # target variable wasn't found in context; fail silently. + context[self.var_name] = [] + return "" + # List of dictionaries in the format: + # {'grouper': 'key', 'list': [list of contents]}. + + # ---- + # Try to resolve the filter expression from the template context. + # If the variable doesn't exist, accept the value that passed to the + # template tag and convert it to a string + # ---- + try: + exp = self.expression.resolve(context) + except template.VariableDoesNotExist: + exp = str(self.expression) + + filter_exp = self.parser.compile_filter(exp) + + context[self.var_name] = [ + {"grouper": key, "list": list(val)} + for key, val in groupby( + obj_list, lambda v, f=filter_exp.resolve: f(v, True) + ) + ] + + return "" + + +@register.tag +def dynamic_regroup(parser, token): + """ + A template tag that allows dynamic grouping of objects based on a provided attribute. + + Usage: {% dynamic_regroup target by expression as var_name %} + + :param parser: The template parser. + :param token: The tokenized tag contents. + :return: A DynamicRegroupNode object. + :raises TemplateSyntaxError: If the tag is not properly formatted. + """ + firstbits = token.contents.split(None, 3) + if len(firstbits) != 4: + raise TemplateSyntaxError("'regroup' tag takes five arguments") + target = parser.compile_filter(firstbits[1]) + if firstbits[2] != "by": + raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") + lastbits_reversed = firstbits[3][::-1].split(None, 2) + if lastbits_reversed[1][::-1] != "as": + raise TemplateSyntaxError( + "next-to-last argument to 'regroup' tag must" " be 'as'" + ) + + # --- + # Django expects the value of `expression` to be an attribute available on + # your objects. The value you pass to the template tag gets converted into a + # FilterExpression object from the literal. + + # Sometimes we need the attribute to group on to be dynamic. So, instead + # of converting the value to a FilterExpression here, we're going to pass the + # value as-is and convert it in the Node. + # ---- + expression = lastbits_reversed[2][::-1] + var_name = lastbits_reversed[0][::-1] + + # ---- + # We also need to hand the parser to the node in order to convert the value + # for `expression` to a FilterExpression. + # ---- + return DynamicRegroupNode(target, parser, expression, var_name) + + +@register.filter(name="any_permission") +def any_permission(user, app_label): + """ + This method is used to check any on the module + + Args: + user (obj): Django user model instance + app_label (str): app label + + Returns: + bool: True if any permission on the module + """ + return user.has_module_perms(app_label) + + +@register.filter +def is_select_multiple(widget): + """ + Custom template filter to check if a widget is an instance of SelectMultiple. + + Usage: + {% load custom_filters %} + + {% if field.field.widget|is_select_multiple %} + + {% endif %} + """ + return isinstance(widget, SelectMultiple) + + +@register.filter +def is_text_area(widget): + """ + Custom template filter to check if a widget is an instance of SelectMultiple. + + Usage: + {% load custom_filters %} + + {% if field.field.widget|Textarea %} + + {% endif %} + """ + return isinstance(widget, Textarea) + + +@register.filter +def base64_encode(value): + try: + return base64.b64encode(value).decode("utf-8") + except: + pass + + +@register.filter(name="current_month_record") +def current_month_record(queryset): + current_month_start_date = datetime.now().replace(day=1) + next_month_start_date = current_month_start_date + timedelta(days=31) + + return queryset.filter( + start_datetime__gte=current_month_start_date, + start_datetime__lt=next_month_start_date, + ).order_by("start_datetime") + + +@register.filter +def get_item(list, i): + try: + return list[i] + except: + return None + + +@register.filter(name="app_installed") +def app_installed(app_name): + """ + Returns True if the app with the given name is installed, otherwise False. + """ + return apps.is_installed(app_name) + + +@register.filter(name="is_stagemanager") +def is_stagemanager(user): + """ + This method is used to check the employee is stage or recruitment manager + """ + try: + employee_obj = user.employee_get + return ( + employee_obj.stage_set.all().exists() + or employee_obj.recruitment_set.exists() + ) + except Exception: + return False + + +@register.filter(name="yes_no") +def yesno(value): + return _("Yes") if value else _("No") + + +@register.filter(name="on_off") +def on_off(value): + if value == "on": + return _("Yes") + elif value == "off": + return _("No") diff --git a/base/urls.py b/base/urls.py index a51d67f27..0172a9ade 100644 --- a/base/urls.py +++ b/base/urls.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from base import announcement, request_and_approve, views from base.forms import ( AttendanceAllowedIPForm, + MailTemplateForm, RotatingShiftAssignForm, RotatingShiftForm, RotatingWorkTypeAssignForm, @@ -20,6 +21,7 @@ from base.models import ( EmployeeShift, EmployeeShiftSchedule, EmployeeType, + HorillaMailTemplate, JobPosition, JobRole, RotatingShift, @@ -67,7 +69,6 @@ urlpatterns = [ views.initialize_database_job_position, name="initialize-database-job-position", ), - path("404", views.custom404, name="404"), path( "initialize-job-position-edit/", views.initialize_job_position_edit, @@ -78,6 +79,7 @@ urlpatterns = [ views.initialize_job_position_delete, name="initialize-job-position-delete", ), + path("404", views.custom404, name="404"), path("login/", views.login_user, name="login"), path( "forgot-password", @@ -160,6 +162,36 @@ urlpatterns = [ path( "replace-primary-mail", views.replace_primary_mail, name="replace-primary-mail" ), + path( + "configuration/view-mail-templates/", + views.view_mail_templates, + name="view-mail-templates", + ), + path( + "view-mail-template//", + views.view_mail_template, + name="view-mail-template", + ), + path( + "create-mail-template/", + views.create_mail_templates, + name="create-mail-template", + ), + path( + "duplicate-mail-template//", + views.object_duplicate, + name="duplicate-mail-template", + kwargs={ + "model": HorillaMailTemplate, + "form": MailTemplateForm, + "template": "mail/htmx/form.html", + }, + ), + path( + "delete-mail-template/", + views.delete_mail_templates, + name="delete-mail-template", + ), path("settings/company-create/", views.company_create, name="company-create"), path("settings/company-view/", views.company_view, name="company-view"), path( @@ -446,16 +478,6 @@ urlpatterns = [ "redirect": "/settings/rotating-shift-view", }, ), - path( - "settings/department-manager-view/", - views.view_department_managers, - name="department-manager-view", - ), - path( - "settings/candidate-reject-reasons/", - views.candidate_reject_reasons, - name="candidate-reject-reasons", - ), path( "employee/rotating-shift-assign/", views.rotating_shift_assign, @@ -711,36 +733,6 @@ urlpatterns = [ views.enable_account_block_unblock, name="enable-account-block-unblock", ), - path( - "settings/attendance-settings-view/", - views.validation_condition_view, - name="attendance-settings-view", - ), - path( - "settings/track-late-come-early-out", - views.track_late_come_early_out, - name="track-late-come-early-out", - ), - path( - "settings/enable-disable-tracking-late-come-early-out", - views.enable_disable_tracking_late_come_early_out, - name="enable-disable-tracking-late-come-early-out", - ), - path( - "settings/grace-settings-view/", - views.grace_time_view, - name="grace-settings-view", - ), - path( - "settings/attendance-settings-create/", - views.validation_condition_create, - name="attendance-settings-create", - ), - path( - "settings/attendance-settings-update//", - views.validation_condition_update, - name="attendance-settings-update", - ), path( "rwork-individual-view//", views.rotating_work_individual_view, @@ -779,22 +771,7 @@ urlpatterns = [ views.rotating_work_type_select_filter, name="r-work-type-select-filter", ), - path("settings/ticket-type-view/", views.ticket_type_view, name="ticket-type-view"), - path("ticket-type-create", views.ticket_type_create, name="ticket-type-create"), - path( - "ticket-type-update/", - views.ticket_type_update, - name="ticket-type-update", - ), - path( - "ticket-type-delete/", - views.ticket_type_delete, - name="ticket-type-delete", - ), path("settings/tag-view/", views.tag_view, name="tag-view"), - path( - "settings/employee-tag-view/", views.employee_tag_view, name="employee-tag-view" - ), path( "settings/helpdesk-tag-view/", views.helpdesk_tag_view, name="helpdesk-tag-view" ), @@ -806,18 +783,6 @@ urlpatterns = [ name="tag-delete", kwargs={"model": Tags, "redirect": "/settings/tag-view/"}, ), - path("employee-tag-create", views.employee_tag_create, name="employee-tag-create"), - path( - "employee-tag-update/", - views.employee_tag_update, - name="employee-tag-update", - ), - path( - "employee-tag-delete//", - views.object_delete, - name="employee-tag-delete", - kwargs={"model": EmployeeTag, "redirect": "/settings/tag-view/"}, - ), path("audit-tag-create", views.audit_tag_create, name="audit-tag-create"), path( "audit-tag-update/", views.audit_tag_update, name="audit-tag-update" @@ -918,36 +883,6 @@ urlpatterns = [ request_and_approve.dashboard_work_type_request, name="dashboard-work-type-request", ), - path( - "dashboard-overtime-approve", - request_and_approve.dashboard_overtime_approve, - name="dashboard-overtime-approve", - ), - path( - "dashboard-attendance-validate", - request_and_approve.dashboard_attendance_validate, - name="dashboard-attendance-validate", - ), - path( - "leave-request-and-approve", - request_and_approve.leave_request_and_approve, - name="leave-request-and-approve", - ), - path( - "leave-allocation-approve", - request_and_approve.leave_allocation_approve, - name="leave-allocation-approve", - ), - path( - "dashboard-feedback-answer", - request_and_approve.dashboard_feedback_answer, - name="dashboard-feedback-answer", - ), - path( - "dashboard-asset-request-approve", - request_and_approve.dashboard_asset_request_approve, - name="dashboard-asset-request-approve", - ), path( "settings/pagination-settings-view/", views.pagination_settings_view, @@ -1028,45 +963,55 @@ urlpatterns = [ name="emp-workinfo-complete", ), path( - "settings/allowed-ips/", - views.allowed_ips, - name="allowed-ips", + "get-horilla-installed-apps/", + views.get_horilla_installed_apps, + name="get-horilla-installed-apps", + ), + path("configuration/holiday-view", views.holiday_view, name="holiday-view"), + path( + "configuration/holidays-excel-template", + views.holidays_excel_template, + name="holidays-excel-template", ), path( - "settings/enable-ip-restriction/", - views.enable_ip_restriction, - name="enable-ip-restriction", + "holidays-info-import", views.holidays_info_import, name="holidays-info-import" + ), + path("holiday-info-export", views.holiday_info_export, name="holiday-info-export"), + path("holiday-creation", views.holiday_creation, name="holiday-creation"), + path("holiday-update/", views.holiday_update, name="holiday-update"), + path("holiday-delete/", views.holiday_delete, name="holiday-delete"), + path( + "holidays-bulk-delete", views.bulk_holiday_delete, name="holidays-bulk-delete" + ), + path("holiday-filter", views.holiday_filter, name="holiday-filter"), + path("holiday-select/", views.holiday_select, name="holiday-select"), + path( + "holiday-select-filter/", + views.holiday_select_filter, + name="holiday-select-filter", ), path( - "settings/add-remove-ip-fields/", - views.add_remove_dynamic_fields, - name="add-remove-ip-fields", - kwargs={ - "model": AttendanceAllowedIP, - "form_class": AttendanceAllowedIPForm, - "template": "attendance/ip_restriction/add_more_ip_fields.html", - "field_type": "character", - "field_name_pre": "ip_address", - }, + "company-leave-creation", + views.company_leave_creation, + name="company-leave-creation", ), path( - "settings/create-allowed-ip/", - views.create_allowed_ips, - name="create-allowed-ip", + "configuration/company-leave-view", + views.company_leave_view, + name="company-leave-view", ), path( - "settings/delete-allowed-ip/", - views.delete_allowed_ips, - name="delete-allowed-ip", + "company-leave-update/", + views.company_leave_update, + name="company-leave-update", ), path( - "settings/edit-allowed-ip/", - views.edit_allowed_ips, - name="edit-allowed-ip", + "company-leave-delete/", + views.company_leave_delete, + name="company-leave-delete", ), path( - "settings/skills-view/", - views.skills_view, - name="skills-view", + "company-leave-filter", views.company_leave_filter, name="company-leave-filter" ), + path("view-penalties", views.view_penalties, name="view-penalties"), ] diff --git a/base/views.py b/base/views.py index e7c463629..cd4ead945 100644 --- a/base/views.py +++ b/base/views.py @@ -5,11 +5,11 @@ This module is used to map url pattens with django views or methods """ import json -import uuid from datetime import datetime, timedelta from os import path from urllib.parse import parse_qs, unquote, urlencode +import pandas as pd from django import forms from django.apps import apps from django.conf import settings @@ -30,14 +30,15 @@ from django.utils.translation import gettext as _ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods -from attendance.forms import AttendanceValidationConditionForm -from attendance.models import AttendanceValidationCondition, GraceTime from base.backends import ConfiguredEmailBackend from base.decorators import ( shift_request_change_permission, work_type_request_change_permission, ) from base.filters import ( + CompanyLeaveFilter, + HolidayFilter, + PenaltyFilter, RotatingShiftAssignFilters, RotatingShiftRequestReGroup, RotatingWorkTypeAssignFilter, @@ -51,11 +52,10 @@ from base.forms import ( AnnouncementExpireForm, AssignPermission, AssignUserGroup, - AttendanceAllowedIPForm, - AttendanceAllowedIPUpdateForm, AuditTagForm, ChangePasswordForm, CompanyForm, + CompanyLeaveForm, DepartmentForm, DriverForm, DynamicMailConfForm, @@ -64,10 +64,12 @@ from base.forms import ( EmployeeShiftForm, EmployeeShiftScheduleForm, EmployeeShiftScheduleUpdateForm, - EmployeeTagForm, EmployeeTypeForm, + HolidayForm, + HolidaysColumnExportForm, JobPositionForm, JobRoleForm, + MailTemplateForm, MultipleApproveConditionForm, PassWordResetForm, ResetPasswordForm, @@ -85,7 +87,6 @@ from base.forms import ( ShiftRequestCommentForm, ShiftRequestForm, TagsForm, - TrackLateComeEarlyOutForm, UserGroupForm, WorkTypeForm, WorkTypeRequestColumnForm, @@ -102,13 +103,15 @@ from base.methods import ( sortby, ) from base.models import ( + WEEK_DAYS, + WEEKS, Announcement, AnnouncementExpire, AnnouncementView, - AttendanceAllowedIP, BaserequestFile, BiometricAttendance, Company, + CompanyLeaves, DashboardEmployeeCharts, Department, DynamicEmailConfiguration, @@ -117,6 +120,8 @@ from base.models import ( EmployeeShiftDay, EmployeeShiftSchedule, EmployeeType, + Holidays, + HorillaMailTemplate, JobPosition, JobRole, MultipleApprovalCondition, @@ -127,35 +132,29 @@ from base.models import ( ShiftRequest, ShiftRequestComment, Tags, - TrackLateComeEarlyOut, WorkType, WorkTypeRequest, WorkTypeRequestComment, ) from employee.filters import EmployeeFilter from employee.forms import ActiontypeForm -from employee.models import Actiontype, Employee, EmployeeTag, EmployeeWorkInformation -from helpdesk.forms import TicketTypeForm -from helpdesk.models import DepartmentManager, TicketType +from employee.models import Actiontype, Employee, EmployeeWorkInformation from horilla.decorators import ( delete_permission, duplicate_permission, hx_request_required, + install_required, login_required, manager_can_enter, permission_required, ) from horilla.group_by import group_by_queryset +from horilla.methods import get_horilla_model_class from horilla_audit.forms import HistoryTrackingFieldsForm from horilla_audit.models import AccountBlockUnblock, AuditTag, HistoryTrackingFields from notifications.base.models import AbstractNotification from notifications.models import Notification from notifications.signals import notify -from payroll.forms.component_forms import PayrollSettingsForm -from payroll.models.models import EncashmentGeneralSettings -from payroll.models.tax_models import PayrollSettings -from pms.models import KeyResult -from recruitment.models import RejectReason, Skill def custom404(request): @@ -747,28 +746,6 @@ def common_settings(request): return render(request, "settings.html") -@login_required -def view_department_managers(request): - department_managers = DepartmentManager.objects.all() - - context = { - "department_managers": department_managers, - } - return render(request, "department_managers/department_managers.html", context) - - -@login_required -@permission_required("recruitment.view_rejectreason") -def candidate_reject_reasons(request): - """ - This method is used to view all the reject reasons - """ - reject_reasons = RejectReason.objects.all() - return render( - request, "settings/reject_reasons.html", {"reject_reasons": reject_reasons} - ) - - @login_required @hx_request_required @permission_required("auth.add_group") @@ -1065,7 +1042,8 @@ def object_delete(request, id, **kwargs): _("This {} is already in use for {}.").format(instance, model_names_str), ), - if redirect_path == "/pms/filter-key-result/": + if apps.is_installed("pms") and redirect_path == "/pms/filter-key-result/": + KeyResult = get_horilla_model_class(app_label="pms", model="keyresult") key_results = KeyResult.objects.all() if key_results.exists(): previous_data = request.GET.urlencode() @@ -1325,7 +1303,6 @@ def replace_primary_mail(request): messages.success(request, "Primary Mail server configuration replaced") return redirect("mail-server-conf") - # return HttpResponse("") @login_required @@ -1347,6 +1324,76 @@ def mail_server_create_or_update(request): ) +@login_required +@permission_required("base.view_horillamailtemplate") +def view_mail_templates(request): + """ + This method will render template to disply the offerletter templates + """ + templates = HorillaMailTemplate.objects.all() + form = MailTemplateForm() + if templates.exists(): + template = "mail/view_templates.html" + else: + template = "mail/empty_mail_template.html" + searchWords = form.get_template_language() + return render( + request, + template, + {"templates": templates, "form": form, "searchWords": searchWords}, + ) + + +@login_required +@hx_request_required +@permission_required("base.change_horillamailtemplate") +def view_mail_template(request, obj_id): + """ + This method is used to display the template/form to edit + """ + template = HorillaMailTemplate.objects.get(id=obj_id) + form = MailTemplateForm(instance=template) + searchWords = form.get_template_language() + if request.method == "POST": + form = MailTemplateForm(request.POST, instance=template) + if form.is_valid(): + form.save() + messages.success(request, "Template updated") + return HttpResponse("") + + return render( + request, + "mail/htmx/form.html", + {"form": form, "duplicate": False, "searchWords": searchWords}, + ) + + +@login_required +@require_http_methods(["POST"]) +@permission_required("base.add_horillamailtemplate") +def create_mail_templates(request): + """ + This method is used to create offerletter template + """ + if request.method == "POST": + form = MailTemplateForm(request.POST) + if form.is_valid(): + instance = form.save() + instance.save() + messages.success(request, "Template created") + return HttpResponse("") + return redirect(view_mail_templates) + + +@login_required +@permission_required("base.delete_horillamailtemplate") +def delete_mail_templates(request): + ids = request.GET.getlist("ids") + result = HorillaMailTemplate.objects.filter(id__in=ids).delete() + messages.success(request, "Template deleted") + return redirect(view_mail_templates) + + @login_required @hx_request_required @permission_required("base.add_company") @@ -2225,7 +2272,11 @@ def employee_shift_view(request): """ shifts = EmployeeShift.objects.all() - grace_times = GraceTime.objects.all().exclude(is_default=True) + if apps.is_installed("attendance"): + GraceTime = get_horilla_model_class(app_label="attendance", model="gracetime") + grace_times = GraceTime.objects.all().exclude(is_default=True) + else: + grace_times = None return render( request, "base/shift/shift.html", {"shifts": shifts, "grace_times": grace_times} ) @@ -2840,7 +2891,7 @@ def employee_permission_assign(request): ).distinct() context["show_assign"] = True permissions = [] - apps = [ + horilla_apps = [ "base", "recruitment", "employee", @@ -2855,7 +2906,8 @@ def employee_permission_assign(request): "horilla_documents", "helpdesk", ] - for app_name in apps: + installed_apps = [app for app in settings.INSTALLED_APPS if app in horilla_apps] + for app_name in installed_apps: app_models = [] for model in get_models_in_app(app_name): app_models.append( @@ -2864,7 +2916,9 @@ def employee_permission_assign(request): "model_name": model._meta.model_name, } ) - permissions.append({"app": app_name.capitalize(), "app_models": app_models}) + permissions.append( + {"app": app_name.capitalize().replace("_", " "), "app_models": app_models} + ) context["permissions"] = permissions context["employees"] = paginator_qry(employees, request.GET.get("page")) return render( @@ -2926,9 +2980,6 @@ def employee_permission_search(request, codename=None, uid=None): ) -# add_recruitment - - @login_required @require_http_methods(["POST"]) @permission_required("auth.add_permission") @@ -3847,12 +3898,6 @@ def shift_request_search(request): data_dict = parse_qs(previous_data) template = "shift_request/htmx/requests.html" - # if field != "" and field is not None: - # field_copy = field.replace(".", "__") - # shift_requests = shift_requests.order_by(f"-{field_copy}") - # allocated_shift_requests = allocated_shift_requests.order_by(f"-{field_copy}") - # template = "shift_request/htmx/group_by.html" - if field != "" and field is not None: shift_requests = group_by_queryset( shift_requests, field, request.GET.get("page"), "page" @@ -4601,16 +4646,30 @@ def general_settings(request): """ This method is used to render settings template """ - from payroll.forms.forms import EncashmentGeneralSettingsForm + if apps.is_installed("payroll"): + PayrollSettings = get_horilla_model_class( + app_label="payroll", model="payrollsettings" + ) + EncashmentGeneralSettings = get_horilla_model_class( + app_label="payroll", model="encashmentgeneralsettings" + ) + from payroll.forms.component_forms import PayrollSettingsForm + from payroll.forms.forms import EncashmentGeneralSettingsForm + + currency_instance = PayrollSettings.objects.first() + currency_form = PayrollSettingsForm(instance=currency_instance) + encashment_instance = EncashmentGeneralSettings.objects.first() + encashment_form = EncashmentGeneralSettingsForm(instance=encashment_instance) + else: + encashment_form = None + currency_form = None instance = AnnouncementExpire.objects.first() form = AnnouncementExpireForm(instance=instance) - encashment_instance = EncashmentGeneralSettings.objects.first() enabled_block_unblock = ( AccountBlockUnblock.objects.exists() and AccountBlockUnblock.objects.first().is_enabled ) - encashment_form = EncashmentGeneralSettingsForm(instance=encashment_instance) history_tracking_instance = HistoryTrackingFields.objects.first() history_fields_form_initial = {} if history_tracking_instance and history_tracking_instance.tracking_fields: @@ -4620,8 +4679,7 @@ def general_settings(request): ] } history_fields_form = HistoryTrackingFieldsForm(initial=history_fields_form_initial) - currency_instance = PayrollSettings.objects.first() - currency_form = PayrollSettingsForm(instance=currency_instance) + if DynamicPagination.objects.filter(user_id=request.user).exists(): pagination = DynamicPagination.objects.filter(user_id=request.user).first() pagination_form = DynamicPaginationForm(instance=pagination) @@ -4819,114 +4877,6 @@ def enable_account_block_unblock(request): return redirect(general_settings) -@login_required -@permission_required("attendance.view_attendancevalidationcondition") -def validation_condition_view(request): - """ - This method view attendance validation conditions. - """ - condition = AttendanceValidationCondition.objects.first() - default_grace_time = GraceTime.objects.filter(is_default=True).first() - return render( - request, - "attendance/break_point/condition.html", - {"condition": condition, "default_grace_time": default_grace_time}, - ) - - -@login_required -@permission_required("base.view_tracklatecomeearlyout") -def track_late_come_early_out(request): - tracking = TrackLateComeEarlyOut.objects.first() - form = TrackLateComeEarlyOutForm( - initial={"is_enable": tracking.is_enable} if tracking else {} - ) - return render( - request, "attendance/late_come_early_out/tracking.html", {"form": form} - ) - - -@login_required -@permission_required("base.change_tracklatecomeearlyout") -def enable_disable_tracking_late_come_early_out(request): - if request.method == "POST": - enable = bool(request.POST.get("is_enable")) - tracking, created = TrackLateComeEarlyOut.objects.get_or_create() - tracking.is_enable = enable - tracking.save() - message = _("enabled") if enable else _("disabled") - messages.success( - request, _("Tracking late come early out {} successfully").format(message) - ) - return HttpResponse("") - - -@login_required -@permission_required("attendance.view_attendancevalidationcondition") -def grace_time_view(request): - """ - This method view attendance validation conditions. - """ - condition = AttendanceValidationCondition.objects.first() - default_grace_time = GraceTime.objects.filter(is_default=True).first() - grace_times = GraceTime.objects.all().exclude(is_default=True) - - return render( - request, - "attendance/grace_time/grace_time.html", - { - "condition": condition, - "default_grace_time": default_grace_time, - "grace_times": grace_times, - }, - ) - - -@login_required -@permission_required("attendance.add_attendancevalidationcondition") -def validation_condition_create(request): - """ - This method render a form to create attendance validation conditions, - and create if the form is valid. - """ - form = AttendanceValidationConditionForm() - if request.method == "POST": - form = AttendanceValidationConditionForm(request.POST) - if form.is_valid(): - form.save() - messages.success(request, _("Attendance Break-point settings created.")) - return HttpResponse("") - return render( - request, - "attendance/break_point/condition_form.html", - {"form": form}, - ) - - -@login_required -@hx_request_required -@permission_required("attendance.change_attendancevalidationcondition") -def validation_condition_update(request, obj_id): - """ - This method is used to update validation condition - Args: - obj_id : validation condition instance id - """ - condition = AttendanceValidationCondition.objects.get(id=obj_id) - form = AttendanceValidationConditionForm(instance=condition) - if request.method == "POST": - form = AttendanceValidationConditionForm(request.POST, instance=condition) - if form.is_valid(): - form.save() - messages.success(request, _("Attendance Break-point settings updated.")) - return HttpResponse("") - return render( - request, - "attendance/break_point/condition_form.html", - {"form": form, "condition": condition}, - ) - - @login_required def shift_select(request): page_number = request.GET.get("page") @@ -5104,90 +5054,6 @@ def rotating_work_type_select_filter(request): return JsonResponse(context) -@login_required -@permission_required("helpdesk.view_tickettype") -def ticket_type_view(request): - """ - This method is used to show Ticket type - """ - ticket_types = TicketType.objects.all() - return render( - request, "base/ticket_type/ticket_type.html", {"ticket_types": ticket_types} - ) - - -@login_required -@hx_request_required -@permission_required("helpdesk.create_tickettype") -def ticket_type_create(request): - """ - This method renders form and template to create Ticket type - """ - form = TicketTypeForm() - if request.method == "POST": - form = TicketTypeForm(request.POST) - if request.GET.get("ajax"): - if form.is_valid(): - instance = form.save() - response = { - "errors": "no_error", - "ticket_id": instance.id, - "title": instance.title, - } - return JsonResponse(response) - - errors = form.errors.as_json() - return JsonResponse({"errors": errors}) - if form.is_valid(): - form.save() - form = TicketTypeForm() - messages.success(request, _("Ticket type has been created successfully!")) - return HttpResponse("") - return render( - request, - "base/ticket_type/ticket_type_form.html", - { - "form": form, - }, - ) - - -@login_required -@hx_request_required -@permission_required("helpdesk.update_tickettype") -def ticket_type_update(request, t_type_id): - """ - This method renders form and template to create Ticket type - """ - ticket_type = TicketType.objects.get(id=t_type_id) - form = TicketTypeForm(instance=ticket_type) - if request.method == "POST": - form = TicketTypeForm(request.POST, instance=ticket_type) - if form.is_valid(): - form.save() - form = TicketTypeForm() - messages.success(request, _("Ticket type has been updated successfully!")) - return HttpResponse("") - return render( - request, - "base/ticket_type/ticket_type_form.html", - {"form": form, "t_type_id": t_type_id}, - ) - - -@login_required -@require_http_methods(["POST", "DELETE"]) -@permission_required("helpdesk.delete_tickettype") -def ticket_type_delete(request, t_type_id): - ticket_type = TicketType.find(t_type_id) - if ticket_type: - ticket_type.delete() - messages.success(request, _("Ticket type has been deleted successfully!")) - else: - messages.error(request, _("Ticket type not found")) - return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) - - @login_required @permission_required("horilla_audit.view_audittag") def tag_view(request): @@ -5202,20 +5068,6 @@ def tag_view(request): ) -@login_required -@permission_required("employee.view_employeetag") -def employee_tag_view(request): - """ - This method is used to Employee tags - """ - employeetags = EmployeeTag.objects.all() - return render( - request, - "base/tags/employee_tags.html", - {"employeetags": employeetags}, - ) - - @login_required @permission_required("helpdesk.view_tag") def helpdesk_tag_view(request): @@ -5277,53 +5129,6 @@ def tag_update(request, tag_id): ) -@login_required -@hx_request_required -@permission_required("employee.add_employeetag") -def employee_tag_create(request): - """ - This method renders form and template to create Ticket type - """ - form = EmployeeTagForm() - if request.method == "POST": - form = EmployeeTagForm(request.POST) - if form.is_valid(): - form.save() - form = EmployeeTagForm() - messages.success(request, _("Tag has been created successfully!")) - return HttpResponse("") - return render( - request, - "base/employee_tag/employee_tag_form.html", - { - "form": form, - }, - ) - - -@login_required -@hx_request_required -@permission_required("employee.add_employeetag") -def employee_tag_update(request, tag_id): - """ - This method renders form and template to create Ticket type - """ - tag = EmployeeTag.objects.get(id=tag_id) - form = EmployeeTagForm(instance=tag) - if request.method == "POST": - form = EmployeeTagForm(request.POST, instance=tag) - if form.is_valid(): - form.save() - form = EmployeeTagForm() - messages.success(request, _("Tag has been updated successfully!")) - return HttpResponse("") - return render( - request, - "base/employee_tag/employee_tag_form.html", - {"form": form, "tag_id": tag_id}, - ) - - @login_required @hx_request_required @permission_required("horilla_audit.add_audittag") @@ -5372,6 +5177,7 @@ def audit_tag_update(request, tag_id): @login_required +@install_required @permission_required("base.view_multipleapprovalcondition") def multiple_approval_condition(request): form = MultipleApproveConditionForm() @@ -5544,6 +5350,9 @@ def multiple_level_approval_edit(request, condition_id): form = MultipleApproveConditionForm(request.POST, instance=condition) if form.is_valid(): instance = form.save() + messages.success( + request, _("Multiple approval condition updated successfully") + ) sequence = 0 MultipleApprovalManagers.objects.filter(condition_id=condition).delete() for key, value in request.POST.items(): @@ -5555,8 +5364,6 @@ def multiple_level_approval_edit(request, condition_id): sequence=sequence, employee_id=employee_id, ) - return HttpResponse("") - conditions = MultipleApprovalCondition.objects.all().order_by("department")[::-1] return render( request, @@ -6096,17 +5903,45 @@ def employee_charts(request): return HttpResponse("") -def check_permission(request, charts): +def check_chart_permission(request, charts): """ This function is used to check the permissions for the charts Args: charts: dashboard charts """ - from recruitment.templatetags.recruitmentfilters import ( - is_recruitmentmangers, - is_stagemanager, - ) + from base.templatetags.basefilters import is_reportingmanager + if apps.is_installed("recruitment"): + from recruitment.templatetags.recruitmentfilters import is_stagemanager + + need_stage_manager = [ + "hired_candidates", + "onboarding_candidates", + "recruitment_analytics", + ] + chart_apps = { + "offline_employees": "attendance", + "online_employees": "attendance", + "overall_leave_chart": "leave", + "hired_candidates": "recruitment", + "onboarding_candidates": "onboarding", + "recruitment_analytics": "recruitment", + "attendance_analytic": "attendance", + "hours_chart": "attendance", + "objective_status": "pms", + "key_result_status": "pms", + "feedback_status": "pms", + "shift_request_approve": "base", + "work_type_request_approve": "base", + "overtime_approve": "attendance", + "attendance_validate": "attendance", + "leave_request_approve": "leave", + "leave_allocation_approve": "leave", + "asset_request_approve": "asset", + "employees_chart": "employee", + "gender_chart": "employee", + "department_chart": "base", + } permissions = { "offline_employees": "employee.view_employee", "online_employees": "employee.view_employee", @@ -6128,7 +5963,7 @@ def check_permission(request, charts): "asset_request_approve": "asset.change_assetrequest", } chart_list = [] - need_recruitment_manager = [ + need_reporting_manager = [ "offline_employees", "online_employees", "attendance_analytic", @@ -6144,27 +5979,25 @@ def check_permission(request, charts): "leave_allocation_approve", "asset_request_approve", ] - need_stage_manager = [ - "hired_candidates", - "onboarding_candidates", - "recruitment_analytics", - ] for chart in charts: - if ( - chart[0] in permissions.keys() - or chart[0] in need_recruitment_manager - or chart[0] in need_stage_manager - ): - if request.user.has_perm(permissions[chart[0]]): + if apps.is_installed(chart_apps.get(chart[0])): + if ( + chart[0] in permissions.keys() + or chart[0] in need_reporting_manager + or (apps.is_installed("recruitment") and chart[0] in need_stage_manager) + ): + if request.user.has_perm(permissions[chart[0]]): + chart_list.append(chart) + elif chart[0] in need_reporting_manager: + if is_reportingmanager(request.user): + chart_list.append(chart) + elif ( + apps.is_installed("recruitment") and chart[0] in need_stage_manager + ): + if is_stagemanager(request.user): + chart_list.append(chart) + else: chart_list.append(chart) - elif chart[0] in need_recruitment_manager: - if is_recruitmentmangers(request.user): - chart_list.append(chart) - elif chart[0] in need_stage_manager: - if is_stagemanager(request.user): - chart_list.append(chart) - else: - chart_list.append(chart) return chart_list @@ -6186,7 +6019,7 @@ def employee_chart_show(request): ("recruitment_analytics", _("Recruitment Analytics")), ("attendance_analytic", _("Attendance analytics")), ("hours_chart", _("Hours Chart")), - ("employees_chart", _("Employee Chart")), + ("employees_chart", _("Employees Chart")), ("department_chart", _("Department Chart")), ("gender_chart", _("Gender Chart")), ("objective_status", _("Objective Status")), @@ -6201,7 +6034,8 @@ def employee_chart_show(request): ("feedback_answer", _("Feedbacks to Answer")), ("asset_request_approve", _("Asset Request to Approve")), ] - charts = check_permission(request, charts) + charts = check_chart_permission(request, charts) + if request.method == "POST": employee_charts.charts = [] employee_charts.save() @@ -6258,151 +6092,505 @@ def activate_biometric_attendance(request): @login_required -@permission_required("attendance.add_attendance") -def allowed_ips(request): - """ - This function is used to view the allowed ips - """ - allowed_ips = AttendanceAllowedIP.objects.first() - return render( - request, - "attendance/ip_restriction/ip_restriction.html", - {"allowed_ips": allowed_ips}, +def get_horilla_installed_apps(request): + installed_apps = settings.INSTALLED_APPS + return JsonResponse({"installed_apps": installed_apps}) + + +def generate_error_report(error_list, error_data, file_name): + for item in error_list: + for key, value in error_data.items(): + if key in item: + value.append(item[key]) + else: + value.append(None) + + keys_to_remove = [ + key for key, value in error_data.items() if all(v is None for v in value) + ] + for key in keys_to_remove: + del error_data[key] + + data_frame = pd.DataFrame(error_data, columns=error_data.keys()) + styled_data_frame = data_frame.style.map( + lambda x: "text-align: center", subset=pd.IndexSlice[:, :] ) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = f'attachment; filename="{file_name}"' + writer = pd.ExcelWriter(response, engine="xlsxwriter") + styled_data_frame.to_excel(writer, index=False, sheet_name="Sheet1") -@login_required -@permission_required("attendance.add_attendance") -def enable_ip_restriction(request): - """ - This function is used to enable the allowed ips - """ - form = AttendanceAllowedIPForm() - if request.method == "POST": - ip_restiction = AttendanceAllowedIP.objects.first() + worksheet = writer.sheets["Sheet1"] + worksheet.set_column("A:Z", 30) - if not ip_restiction: - ip_restiction = AttendanceAllowedIP.objects.create(is_enabled=True) - return HttpResponse("") - - if not ip_restiction.is_enabled: - ip_restiction.is_enabled = True - elif ip_restiction.is_enabled: - ip_restiction.is_enabled = False - - ip_restiction.save() - return HttpResponse("") - - -def validate_ip_address(self, value): - """ - This function is used to check if the provided IP is in the ipv4 or ipv6 format. - - Args: - value: The IP address to validate - """ - try: - validate_ipv46_address(value) - except ValidationError: - raise ValidationError("Enter a valid IPv4 or IPv6 address.") - return value + writer.close() + return response @login_required -@permission_required("attendance.add_attendance") -def create_allowed_ips(request): +@hx_request_required +@permission_required("leave.add_holiday") +def holiday_creation(request): """ - This function is used to create the allowed ips + function used to create holidays. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return holiday creation form template + POST : return holiday view template """ - form = AttendanceAllowedIPForm() + + query_string = request.GET.urlencode() + if query_string.startswith("pd="): + previous_data = unquote(query_string[len("pd=") :]) + else: + previous_data = unquote(query_string) + form = HolidayForm() if request.method == "POST": - form = AttendanceAllowedIPForm(request.POST) + form = HolidayForm(request.POST) if form.is_valid(): - values = [request.POST[key] for key in request.POST.keys()] - allowed_ips = AttendanceAllowedIP.objects.first() - for value in values: - try: - validate_ipv46_address(value) - if value not in allowed_ips.additional_data["allowed_ips"]: - allowed_ips.additional_data["allowed_ips"].append(value) - messages.success(request, f"IP address saved successfully") - else: - messages.error(request, "IP address already exists") - - except ValidationError: - messages.error( - request, f"Enter a valid IPv4 or IPv6 address: {value}" - ) - - allowed_ips.save() - - return HttpResponse("") + form.save() + messages.success(request, _("New holiday created successfully..")) + if Holidays.objects.filter().count() == 1: + return HttpResponse("") return render( - request, "attendance/ip_restriction/restrict_form.html", {"form": form} + request, "holiday/holiday_form.html", {"form": form, "pd": previous_data} + ) + + +def holidays_excel_template(request): + try: + columns = [ + "Holiday Name", + "Start Date", + "End Date", + "Recurring", + ] + data_frame = pd.DataFrame(columns=columns) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = ( + 'attachment; filename="assign_leave_type_excel.xlsx"' + ) + data_frame.to_excel(response, index=False) + print(response) + return response + except Exception as exception: + return HttpResponse(exception) + + +@login_required +@permission_required("base.add_holiday") +def holidays_info_import(request): + file_name = "HolidaysImportError.xlsx" + error_list = [] + error_data = { + "Holiday Name": [], + "Start Date": [], + "End Date": [], + "Recurring": [], + "Error1": [], + "Error2": [], + "Error3": [], + "Error4": [], + } + + if request.method == "POST": + file = request.FILES["holidays_import"] + data_frame = pd.read_excel(file) + holiday_dicts = data_frame.to_dict("records") + for holiday in holiday_dicts: + save = True + try: + name = holiday["Holiday Name"] + try: + start_date = pd.to_datetime(holiday["Start Date"]).date() + except Exception as e: + save = False + holiday["Error1"] = _("Invalid start date format {}").format( + holiday["Start Date"] + ) + try: + end_date = pd.to_datetime(holiday["End Date"]).date() + except Exception as e: + save = False + holiday["Error2"] = _("Invalid end date format {}").format( + holiday["End Date"] + ) + if holiday["Recurring"].lower() in ["yes", "no"]: + recurring = True if holiday["Recurring"].lower() == "yes" else False + else: + save = False + holiday["Error3"] = _("Recurring must be {} or {}").format( + "yes", "no" + ) + if save: + holiday = Holidays( + name=name, + start_date=start_date, + end_date=end_date, + recurring=recurring, + ) + holiday.save() + else: + error_list.append(holiday) + except Exception as e: + holiday["Error4"] = f"{str(e)}" + error_list.append(holiday) + + if error_list: + return generate_error_report(error_list, error_data, file_name) + else: + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + + +@login_required +def holiday_info_export(request): + if request.META.get("HTTP_HX_REQUEST"): + export_filter = HolidayFilter() + export_column = HolidaysColumnExportForm() + content = { + "export_filter": export_filter, + "export_column": export_column, + } + return render( + request, "holiday/holiday_export_filter_form.html", context=content + ) + return export_data( + request=request, + model=Holidays, + filter_class=HolidayFilter, + form_class=HolidaysColumnExportForm, + file_name="Holidays_export", ) @login_required -@permission_required("attendance.delete_attendance") -def delete_allowed_ips(request): +def holiday_view(request): """ - This function is used to delete the allowed ips + function used to view holidays. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return holiday view template """ - try: - ids = request.GET.getlist("id") - allowed_ips = AttendanceAllowedIP.objects.first() - ips = allowed_ips.additional_data["allowed_ips"] - for id in ids: - ips.pop(eval(id)) + queryset = Holidays.objects.all()[::-1] + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + page_obj = paginator_qry(queryset, page_number) + holiday_filter = HolidayFilter() - allowed_ips.additional_data["allowed_ips"] = ips - allowed_ips.save() - - messages.success(request, "IP address removed successfully") - except: - messages.error(request, "Invalid id") - return redirect("allowed-ips") + return render( + request, + "holiday/holiday_view.html", + { + "holidays": page_obj, + "form": holiday_filter.form, + "pd": previous_data, + }, + ) @login_required -@permission_required("attendance.change_attendance") -def edit_allowed_ips(request): +@hx_request_required +def holiday_filter(request): """ - This function is used to edit the allowed ips + function used to filter holidays. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return holiday view template """ - try: - - allowed_ips = AttendanceAllowedIP.objects.first() - ips = allowed_ips.additional_data["allowed_ips"] - id = request.GET.get("id") - - form = AttendanceAllowedIPUpdateForm(initial={"ip_address": ips[eval(id)]}) - if request.method == "POST": - form = AttendanceAllowedIPUpdateForm(request.POST) - if form.is_valid(): - new_ip = form.cleaned_data["ip_address"] - ips[eval(id)] = new_ip - if not new_ip in allowed_ips.additional_data["allowed_ips"]: - allowed_ips.additional_data["allowed_ips"] = ips - allowed_ips.save() - messages.success(request, "IP address updated successfully") - else: - messages.error(request, "IP address already exists") - - return HttpResponse("") - except: - messages.error(request, "Invalid id") + queryset = Holidays.objects.all() + previous_data = request.GET.urlencode() + holiday_filter = HolidayFilter(request.GET, queryset).qs + if request.GET.get("sortby"): + holiday_filter = sortby(request, holiday_filter, "sortby") + page_number = request.GET.get("page") + page_obj = paginator_qry(holiday_filter[::-1], page_number) + data_dict = parse_qs(previous_data) + get_key_instances(Holidays, data_dict) return render( request, - "attendance/ip_restriction/restrict_update_form.html", + "holiday/holiday.html", + {"holidays": page_obj, "pd": previous_data, "filter_dict": data_dict}, + ) + + +@login_required +@hx_request_required +@permission_required("base.change_holiday") +def holiday_update(request, id): + """ + function used to update holiday. + + Parameters: + request (HttpRequest): The HTTP request object. + id : holiday id + + Returns: + GET : return holiday update form template + POST : return holiday view template + """ + query_string = request.GET.urlencode() + if query_string.startswith("pd="): + previous_data = unquote(query_string[len("pd=") :]) + else: + previous_data = unquote(query_string) + holiday = Holidays.objects.get(id=id) + form = HolidayForm(instance=holiday) + if request.method == "POST": + form = HolidayForm(request.POST, instance=holiday) + if form.is_valid(): + form.save() + messages.success(request, _("Holidays updated successfully..")) + return render( + request, + "holiday/holiday_update_form.html", + {"form": form, "id": id, "pd": previous_data}, + ) + + +@login_required +@hx_request_required +@permission_required("base.delete_holiday") +def holiday_delete(request, id): + """ + function used to delete holiday. + + Parameters: + request (HttpRequest): The HTTP request object. + id : holiday id + + Returns: + GET : return holiday view template + """ + query_string = request.GET.urlencode() + try: + Holidays.objects.get(id=id).delete() + messages.success(request, _("Holidays deleted successfully..")) + except Holidays.DoesNotExist: + messages.error(request, _("Holidays not found.")) + except ProtectedError: + messages.error(request, _("Related entries exists")) + if not Holidays.objects.filter(): + return HttpResponse("") + return redirect(f"/holiday-filter?{query_string}") + + +@require_http_methods(["POST"]) +@permission_required("base.delete_holiday") +def bulk_holiday_delete(request): + """ + This method is used to delete bulk of holidays + """ + ids = request.POST["ids"] + ids = json.loads(ids) + del_ids = [] + for holiday_id in ids: + try: + holiday = Holidays.objects.get(id=holiday_id) + holiday.delete() + del_ids.append(holiday_id) + except Exception as e: + messages.error(request, _("Holidays not found.")) + messages.success( + request, _("{} Holidays have been successfully deleted.".format(len(del_ids))) + ) + return JsonResponse({"message": "Success"}) + + +@login_required +def holiday_select(request): + page_number = request.GET.get("page") + + if page_number == "all": + employees = Holidays.objects.all() + + employee_ids = [str(emp.id) for emp in employees] + total_count = employees.count() + + context = {"employee_ids": employee_ids, "total_count": total_count} + + return JsonResponse(context, safe=False) + + +@login_required +def holiday_select_filter(request): + page_number = request.GET.get("page") + filtered = request.GET.get("filter") + filters = json.loads(filtered) if filtered else {} + + if page_number == "all": + employee_filter = HolidayFilter(filters, queryset=Holidays.objects.all()) + + # Get the filtered queryset + filtered_employees = employee_filter.qs + + employee_ids = [str(emp.id) for emp in filtered_employees] + total_count = filtered_employees.count() + + context = {"employee_ids": employee_ids, "total_count": total_count} + + return JsonResponse(context) + + +@login_required +@hx_request_required +@permission_required("base.add_companyleave") +def company_leave_creation(request): + """ + function used to create company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave creation form template + POST : return company leave view template + """ + form = CompanyLeaveForm() + if request.method == "POST": + form = CompanyLeaveForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, _("New company leave created successfully..")) + if CompanyLeaves.objects.filter().count() == 1: + return HttpResponse("") + return render( + request, "company_leave/company_leave_creation_form.html", {"form": form} + ) + + +@login_required +def company_leave_view(request): + """ + function used to view company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave view template + """ + queryset = CompanyLeaves.objects.all() + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + page_obj = paginator_qry(queryset, page_number) + company_leave_filter = CompanyLeaveFilter() + return render( + request, + "company_leave/company_leave_view.html", + { + "company_leaves": page_obj, + "weeks": WEEKS, + "week_days": WEEK_DAYS, + "form": company_leave_filter.form, + "pd": previous_data, + }, + ) + + +@login_required +@hx_request_required +def company_leave_filter(request): + """ + function used to filter company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave view template + """ + queryset = CompanyLeaves.objects.all() + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + company_leave_filter = CompanyLeaveFilter(request.GET, queryset).qs + page_obj = paginator_qry(company_leave_filter, page_number) + data_dict = parse_qs(previous_data) + get_key_instances(CompanyLeaves, data_dict) + + return render( + request, + "company_leave/company_leave.html", + { + "company_leaves": page_obj, + "weeks": WEEKS, + "week_days": WEEK_DAYS, + "pd": previous_data, + "filter_dict": data_dict, + }, + ) + + +@login_required +@hx_request_required +@permission_required("base.change_companyleave") +def company_leave_update(request, id): + """ + function used to update company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + id : company leave id + + Returns: + GET : return company leave update form template + POST : return company leave view template + """ + company_leave = CompanyLeaves.objects.get(id=id) + form = CompanyLeaveForm(instance=company_leave) + if request.method == "POST": + form = CompanyLeaveForm(request.POST, instance=company_leave) + if form.is_valid(): + form.save() + messages.success(request, _("Company leave updated successfully..")) + return render( + request, + "company_leave/company_leave_update_form.html", {"form": form, "id": id}, ) @login_required -def skills_view(request): +@hx_request_required +@permission_required("base.delete_companyleave") +def company_leave_delete(request, id): """ - This function is used to view skills page in settings + function used to create company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave creation form template + POST : return company leave view template """ - skills = Skill.objects.all() - return render(request, "settings/skills/skills_view.html", {"skills": skills}) + query_string = request.GET.urlencode() + try: + CompanyLeaves.objects.get(id=id).delete() + messages.success(request, _("Company leave deleted successfully..")) + except CompanyLeaves.DoesNotExist: + messages.error(request, _("Company leave not found.")) + except ProtectedError: + messages.error(request, _("Related entries exists")) + if not CompanyLeaves.objects.filter(): + return HttpResponse("") + return redirect(f"/company-leave-filter?{query_string}") + + +@login_required +@hx_request_required +def view_penalties(request): + """ + This method is used to filter or view the penalties + """ + records = PenaltyFilter(request.GET).qs + return render(request, "penalty/penalty_view.html", {"records": records}) diff --git a/employee/filters.py b/employee/filters.py index 38a4f9b32..ed51357b9 100644 --- a/employee/filters.py +++ b/employee/filters.py @@ -15,7 +15,7 @@ from django.contrib.auth.models import Group, Permission from django.utils.translation import gettext as _ from django_filters import CharFilter, DateFilter -from attendance.models import Attendance +# from attendance.models import Attendance from base.methods import reload_queryset from base.models import WorkType from employee.models import DisciplinaryAction, Employee, Policy @@ -171,11 +171,12 @@ class EmployeeFilter(FilterSet): today = datetime.datetime.now().date() yesterday = today - datetime.timedelta(days=1) - working_employees = Attendance.objects.filter( - attendance_date__gte=yesterday, - attendance_date__lte=today, - attendance_clock_out_date__isnull=True, - ).values_list("employee_id", flat=True) + # working_employees = Attendance.objects.filter( + # attendance_date__gte=yesterday, + # attendance_date__lte=today, + # attendance_clock_out_date__isnull=True, + # ).values_list("employee_id", flat=True) + working_employees = [] if value: queryset = queryset.filter(id__in=working_employees) else: @@ -236,41 +237,40 @@ class EmployeeFilter(FilterSet): def __init__(self, data=None, queryset=None, *, request=None, prefix=None): super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) - if getattr(request, "exclude_filter_form", False) != True: - self.form.fields["is_active"].initial = True - self.form.fields["email"].widget.attrs["autocomplete"] = "email" - self.form.fields["phone"].widget.attrs["autocomplete"] = "phone" - self.form.fields["country"].widget.attrs["autocomplete"] = "country" - for field in self.form.fields.keys(): - self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" - self.model_choice_filters = [ - filter - for filter in self.filters.values() - if isinstance(filter, django_filters.ModelMultipleChoiceFilter) + self.form.fields["is_active"].initial = True + self.form.fields["email"].widget.attrs["autocomplete"] = "email" + self.form.fields["phone"].widget.attrs["autocomplete"] = "phone" + self.form.fields["country"].widget.attrs["autocomplete"] = "country" + for field in self.form.fields.keys(): + self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" + self.model_choice_filters = [ + filter + for filter in self.filters.values() + if isinstance(filter, django_filters.ModelMultipleChoiceFilter) + ] + for model_choice_filter in self.model_choice_filters: + queryset = ( + model_choice_filter.queryset.filter(is_active=True) + if model_choice_filter.queryset.model == Employee + else model_choice_filter.queryset + ) + choices = [ + ("not_set", _("Not Set")), ] - for model_choice_filter in self.model_choice_filters: - queryset = ( - model_choice_filter.queryset.filter(is_active=True) - if model_choice_filter.queryset.model == Employee - else model_choice_filter.queryset - ) - choices = [ - ("not_set", _("Not Set")), - ] - choices.extend([(obj.id, str(obj)) for obj in queryset]) + choices.extend([(obj.id, str(obj)) for obj in queryset]) - self.form.fields[model_choice_filter.field_name] = ( - forms.MultipleChoiceField( - choices=choices, - required=False, - widget=forms.SelectMultiple( - attrs={ - "class": "oh-select oh-select-2 select2-hidden-accessible", - "id": uuid.uuid4(), - } - ), - ) + self.form.fields[model_choice_filter.field_name] = ( + forms.MultipleChoiceField( + choices=choices, + required=False, + widget=forms.SelectMultiple( + attrs={ + "class": "oh-select oh-select-2 select2-hidden-accessible", + "id": uuid.uuid4(), + } + ), ) + ) class EmployeeReGroup: diff --git a/employee/forms.py b/employee/forms.py index f75d0acd3..cfcc7981a 100644 --- a/employee/forms.py +++ b/employee/forms.py @@ -42,6 +42,7 @@ from employee.models import ( Employee, EmployeeBankDetails, EmployeeNote, + EmployeeTag, EmployeeWorkInformation, NoteFiles, Policy, @@ -681,3 +682,19 @@ class ActiontypeForm(ModelForm): "onchange": "actionChange($(this))", } ) + + +class EmployeeTagForm(ModelForm): + """ + Employee Tags form + """ + + class Meta: + """ + Meta class for additional options + """ + + model = EmployeeTag + fields = "__all__" + exclude = ["is_active"] + widgets = {"color": TextInput(attrs={"type": "color", "style": "height:50px"})} diff --git a/employee/models.py b/employee/models.py index eac4f0889..9de587cfb 100644 --- a/employee/models.py +++ b/employee/models.py @@ -7,11 +7,13 @@ This module is used to register models for employee app from datetime import date, datetime, timedelta +from django.apps import apps from django.conf import settings from django.contrib.auth.models import Permission, User from django.core.exceptions import ValidationError from django.core.files.storage import default_storage from django.db import models +from django.db.models.query import QuerySet from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.translation import gettext as _ @@ -30,6 +32,7 @@ from base.models import ( ) from employee.methods.duration_methods import format_time, strtime_seconds from horilla import horilla_middlewares +from horilla.methods import get_horilla_model_class from horilla.models import HorillaModel from horilla_audit.methods import get_diff from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog @@ -200,42 +203,52 @@ class Employee(models.Model): This method is used to get the leave status of the employee """ today = date.today() - leaves_requests = self.leaverequest_set.filter( - start_date__lte=today, end_date__gte=today + leaves_requests = ( + self.leaverequest_set.filter(start_date__lte=today, end_date__gte=today) + if apps.is_installed("leave") + else QuerySet().none() ) - status = "Expected working" + status = _("Expected working") if leaves_requests.exists(): if leaves_requests.filter(status="approved").exists(): - status = "On Leave" + status = _("On Leave") elif leaves_requests.filter(status="requested"): - status = "Waiting Approval" + status = _("Waiting Approval") else: - status = "Canceled / Rejected" - elif self.employee_attendances.filter( - attendance_date=today, - ).exists(): - status = "On a break" + status = _("Canceled / Rejected") + elif ( + apps.is_installed("attendance") + and self.employee_attendances.filter( + attendance_date=today, + ).exists() + ): + status = _("On a break") return status def get_forecasted_at_work(self): """ This method is used to the employees current day shift status """ - today = datetime.today() - attendance = self.employee_attendances.filter(attendance_date=today).first() - minimum_hour_seconds = strtime_seconds(getattr(attendance, "minimum_hour", "0")) - at_work = 0 - forecasted_pending_hours = 0 - if attendance: - at_work = attendance.get_at_work_from_activities() - forecasted_pending_hours = max(0, (minimum_hour_seconds - at_work)) + if apps.is_installed("attendance"): + today = datetime.today() + attendance = self.employee_attendances.filter(attendance_date=today).first() + minimum_hour_seconds = strtime_seconds( + getattr(attendance, "minimum_hour", "0") + ) + at_work = 0 + forecasted_pending_hours = 0 + if attendance: + at_work = attendance.get_at_work_from_activities() + forecasted_pending_hours = max(0, (minimum_hour_seconds - at_work)) - return { - "forecasted_at_work": format_time(at_work), - "forecasted_pending_hours": format_time(forecasted_pending_hours), - "forecasted_at_work_seconds": at_work, - "forecasted_pending_hours_seconds": forecasted_pending_hours, - } + return { + "forecasted_at_work": format_time(at_work), + "forecasted_pending_hours": format_time(forecasted_pending_hours), + "forecasted_at_work_seconds": at_work, + "forecasted_pending_hours_seconds": forecasted_pending_hours, + } + else: + return {} def get_today_attendance(self): """ @@ -261,25 +274,33 @@ class Employee(models.Model): they are considered eligible for archiving. If they are associated, a dictionary is returned with a list of related models of that employee. """ - from onboarding.models import OnboardingStage, OnboardingTask - from recruitment.models import Recruitment, Stage - + if apps.is_installed("onboarding"): + OnboardingStage = get_horilla_model_class("onboarding", "onboardingstage") + OnboardingTask = get_horilla_model_class("onboarding", "onboardingtask") + onboarding_stage_query = OnboardingStage.objects.filter(employee_id=self.pk) + onboarding_task_query = OnboardingTask.objects.filter(employee_id=self.pk) + else: + onboarding_stage_query = None + onboarding_task_query = None + if apps.is_installed("recruitment"): + Recruitment = get_horilla_model_class("recruitment", "recruitment") + Stage = get_horilla_model_class("recruitment", "stage") + recruitment_stage_query = Stage.objects.filter(stage_managers=self.pk) + recruitment_manager_query = Recruitment.objects.filter( + recruitment_managers=self.pk + ) + else: + recruitment_stage_query = None + recruitment_manager_query = None reporting_manager_query = EmployeeWorkInformation.objects.filter( reporting_manager_id=self.pk ) - recruitment_stage_query = Stage.objects.filter(stage_managers=self.pk) - onboarding_stage_query = OnboardingStage.objects.filter(employee_id=self.pk) - onboarding_task_query = OnboardingTask.objects.filter(employee_id=self.pk) - recruitment_manager_query = Recruitment.objects.filter( - recruitment_managers=self.pk - ) - if not ( reporting_manager_query.exists() - or recruitment_stage_query.exists() - or onboarding_stage_query.exists() - or onboarding_task_query.exists() - or recruitment_manager_query.exists() + or (recruitment_stage_query and recruitment_stage_query.exists()) + or (onboarding_stage_query and onboarding_stage_query.exists()) + or (onboarding_task_query and onboarding_task_query.exists()) + or (recruitment_manager_query and recruitment_manager_query.exists()) ): return False else: @@ -347,22 +368,28 @@ class Employee(models.Model): def check_online(self): """ - This method is used to check the user in online users or not + This method is used to check if the user is in the list of online users. """ - from attendance.models import Attendance + if apps.is_installed("attendance"): + Attendance = get_horilla_model_class("attendance", "attendance") + request = getattr(horilla_middlewares._thread_locals, "request", None) - request = getattr(horilla_middlewares._thread_locals, "request", None) - if not getattr(request, "working_employees", None): - today = datetime.now().date() - yesterday = today - timedelta(days=1) - working_employees = Attendance.objects.filter( - attendance_date__gte=yesterday, - attendance_date__lte=today, - attendance_clock_out_date__isnull=True, - ).values_list("employee_id", flat=True) - setattr(request, "working_employees", working_employees) - working_employees = request.working_employees - return self.pk in working_employees + if request is not None: + if ( + not hasattr(request, "working_employees") + or request.working_employees is None + ): + today = datetime.now().date() + yesterday = today - timedelta(days=1) + working_employees = Attendance.objects.filter( + attendance_date__gte=yesterday, + attendance_date__lte=today, + attendance_clock_out_date__isnull=True, + ).values_list("employee_id", flat=True) + setattr(request, "working_employees", working_employees) + working_employees = request.working_employees + return self.pk in working_employees + return False class Meta: """ diff --git a/employee/not_in_out_dashboard.py b/employee/not_in_out_dashboard.py index c5882d82c..badb32de1 100644 --- a/employee/not_in_out_dashboard.py +++ b/employee/not_in_out_dashboard.py @@ -16,11 +16,11 @@ from django.shortcuts import render from base.backends import ConfiguredEmailBackend from base.methods import generate_pdf +from base.models import HorillaMailTemplate from employee.filters import EmployeeFilter from employee.models import Employee from horilla import settings from horilla.decorators import login_required, manager_can_enter -from recruitment.models import RecruitmentMailTemplate def paginator_qry(qryset, page_number): @@ -83,7 +83,7 @@ def send_mail(request, emp_id=None): employee = Employee.objects.get(id=emp_id) employees = Employee.objects.all() - templates = RecruitmentMailTemplate.objects.all() + templates = HorillaMailTemplate.objects.all() return render( request, "employee/send_mail.html", @@ -96,7 +96,7 @@ def get_template(request, emp_id): """ This method is used to return the mail template """ - body = RecruitmentMailTemplate.objects.get(id=emp_id).body + body = HorillaMailTemplate.objects.get(id=emp_id).body instance_id = request.GET.get("instance_id") if instance_id: instance = Employee.objects.get(id=instance_id) @@ -138,7 +138,7 @@ def send_mail_to_employee(request): template_attachment_ids = request.POST.getlist("template_attachments") for employee in employees: bodys = list( - RecruitmentMailTemplate.objects.filter( + HorillaMailTemplate.objects.filter( id__in=template_attachment_ids ).values_list("body", flat=True) ) diff --git a/employee/templates/dashboard/not_in_yet.html b/employee/templates/dashboard/not_in_yet.html index ba9f19717..3fd6c41a9 100644 --- a/employee/templates/dashboard/not_in_yet.html +++ b/employee/templates/dashboard/not_in_yet.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %}
diff --git a/employee/templates/dashboard/not_out_yet.html b/employee/templates/dashboard/not_out_yet.html index 5d3e25015..0a9177f8b 100644 --- a/employee/templates/dashboard/not_out_yet.html +++ b/employee/templates/dashboard/not_out_yet.html @@ -1,4 +1,5 @@ {% load i18n %} +{% load static %}
{% trans 'Online Employees' %} @@ -41,7 +42,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/employee/templates/dashboard/upcoming_birthdays.html b/employee/templates/dashboard/upcoming_birthdays.html index c8cb6f55c..5e4bd2afc 100644 --- a/employee/templates/dashboard/upcoming_birthdays.html +++ b/employee/templates/dashboard/upcoming_birthdays.html @@ -42,4 +42,4 @@ -{% endblock %} +{% endblock %} diff --git a/employee/templates/disciplinary_actions/disciplinary_nav.html b/employee/templates/disciplinary_actions/disciplinary_nav.html index 4487a7427..b0e1a1acc 100644 --- a/employee/templates/disciplinary_actions/disciplinary_nav.html +++ b/employee/templates/disciplinary_actions/disciplinary_nav.html @@ -182,16 +182,6 @@ {% endif %} - - + - - -
@@ -116,141 +131,127 @@
+
- Username + $('#enlargeImageContainer').addClass('enlarge-image-container');" onmouseout="hideEnlargeImage() + $('#enlargeImageContainer').removeClass('enlarge-image-container');" {% endif %} />
- {% if employee.check_online %} - - - {% else %} - - + {% if "attendance"|app_installed %} + {% if employee.check_online %} + + + {% else %} + + + {% endif %} {% endif %}
+

{{employee}}

- {{employee.job_position_id}} + {{employee.job_position_id}}

-
+ +
  • @@ -259,6 +260,7 @@ {{employee.employee_work_info.email}}
  • +
  • @@ -272,6 +274,7 @@ {% endif %}
  • +
  • @@ -279,6 +282,7 @@ {{employee.employee_work_info.mobile}}
  • +
  • @@ -288,7 +292,8 @@ {{employee.phone}} {% else %} ********** - {% endif %} + {% endif %} +
@@ -296,334 +301,227 @@
{% if request.user.is_superuser or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
-
- -
-
    -
+
+
+ +
+
    +
+
+
-
-
{% endif %}
-
    -
  • - {% trans "About" %} -
  • - {% if request.user == employee.employee_user_id or request.user|check_manager:employee or perms.attendance.view_worktyperequest or perms.attendance.view_shiftrequest %} -
  • - {% trans "Work Type & Shift" %} -
  • - {% endif %} - {% if perms.attendance.view_attendance or request.user|check_manager:employee %} -
  • - {% trans "Attendance" %} -
  • - {% endif %} - {% if perms.leave.view_leaverequest or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
  • - {% trans "Leave" %} -
  • - {% endif %} - {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} -
  • - {% trans "Payroll" %} -
  • - {% endif %} - {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} -
  • - {% trans "Allowance & Deduction" %} -
  • - {% endif %} - {% if perms.attendance.view_penaltyaccount or request.user == employee.employee_user_id %} -
  • - {% trans "Penalty Account" %} -
  • - {% endif %} - {% comment %}
  • - {% trans "Team" %} -
  • {% endcomment %} - {% if perms.employee.view_historicalemployeeworkinformation or request.user|check_manager:employee %} -
  • - {% trans "History" %} -
  • - {% endif %} - {% if perms.asset.view_asset or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
      -
    • - {% trans "Assets" %} +
    • + {% trans "About" %}
    • + + {% if request.user == employee.employee_user_id or request.user|check_manager:employee or perms.attendance.view_worktyperequest or perms.attendance.view_shiftrequest %} +
    • + + {% trans "Work Type & Shift"%} + +
    • {% endif %} - {% if perms.pms.view_feedback or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
    • - {% trans "Performance" %} -
    • + + {% if "attendance"|app_installed %} + {% if perms.attendance.view_attendance or request.user|check_manager:employee %} +
    • + {% trans "Attendance" %} +
    • + {% endif %} {% endif %} + + {% if "leave"|app_installed %} + {% if perms.leave.view_leaverequest or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Leave" %} +
    • + {% endif %} + {% endif %} + + {% if "payroll"|app_installed %} + {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} +
    • + {% trans "Payroll" %} +
    • + {% endif %} + + {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} +
    • + + {% trans "Allowance & Deduction" %} + +
    • + {% endif %} + {% endif %} + + {% if "attendance"|app_installed or "leave"|app_installed %} + {% if perms.attendance.view_penaltyaccount or request.user == employee.employee_user_id %} +
    • + {% trans "Penalty Account" %} +
    • + {% endif %} + {% endif %} + + {% if perms.employee.view_historicalemployeeworkinformation or request.user|check_manager:employee %} +
    • + {% trans "History" %} +
    • + {% endif %} + + {% if "asset"|app_installed %} + {% if perms.asset.view_asset or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Assets" %} + +
    • + {% endif %} + {% endif %} + + {% if "pms"|app_installed %} + {% if perms.pms.view_feedback or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Performance" %} + +
    • + {% endif %} + {% endif %} + {% if perms.auth.view_permission or perms.auth.view_group %} -
    • - {% trans "Groups & Permissions" %} -
    • +
    • + + {% trans "Groups & Permissions" %} + +
    • {% endif %} + {% if perms.employee.view_employeenote or request.user|check_manager:employee %} -
    • - {% trans "Note" %} -
    • +
    • + {% trans "Note" %} + +
    • {% endif %} + {% if perms.horilla_documents.view_document or request.user == employee.employee_user_id %} -
    • - {% trans "Documents" %} -
    • +
    • + + {% trans "Documents" %} + +
    • {% endif %} + {% if perms.employee.view_employee %}
    • - {% trans "Mail Log" %} + {% trans "Mail Log" %}
    • {% endif %} - {% if perms.employee.view_employeenote or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
    • - {% trans "Bonus Points" %} -
    • + + {% if "payroll"|app_installed %} + {% if perms.employee.view_bonuspoint or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Bonus Points" %} +
    • + {% endif %} {% endif %}
    -
    +
    + +
    + +
    + {% include "tabs/bonus_points.html" %}
    -
    +
    + {% include "tabs/note_tab.html" %}
    -
    - {% include "tabs/bonus_points.html" %} -
    -
    - {% include "tabs/note_tab.html" %} -
    +
    -
    -
    + {% if "payroll"|app_installed %} +
    + {% endif %} -
    -
    -
    -
    + {% if "attendance"|app_installed %} +
    + {% endif %} -
    -
    + {% if "attendance"|app_installed or "leave"|app_installed %} +
    + {% include 'tabs/penalty_account.html' %} +
    + {% endif %} -
    - {% include 'tabs/penalty_account.html' %} -
    + {% if "pms"|app_installed %} +
    + {% endif %} -
    -
    + {% if "asset"|app_installed %} +
    + {% endif %} -
    -
    +
    -
    -
    - -
    -
    -
    +
    {% if perms.auth.view_permission or perms.auth.view_group or request.user|is_reportingmanager or request.user == employee.employee_user_id %} {% include "tabs/group_permissions.html" %} {% endif %}
    -
    - {% if request.user|check_manager:employee or request.user == employee.employee_user_id or perms.employee.view_historicalemployeeworkinformation %} + {% if request.user|check_manager:employee or request.user == employee.employee_user_id or perms.employee.view_historicalemployeeworkinformation %} +
    {% include "tabs/history.html" %} - {% endif %} +
    + {% endif %} -
    - -
    + {% if "payroll"|app_installed %} {% if perms.view_payslip or request.user == employee.employee_user_id %} - {% include "tabs/payroll-tab.html" %} +
    + {% include "tabs/payroll-tab.html" %} +
    {% endif %} -
    + {% endif %} -
    - {% if perms.leave.view_leaverequest or perms.leave.view_leavetype or request.user|check_manager:employee or request.user == employee.employee_user_id %} - {% include "tabs/leave-tab.html" %} - {% endif %} -
    + {% if "leave"|app_installed %} +
    + {% endif %}
    @@ -641,16 +539,16 @@ } }); - toggleColumns("employee-tab","fieldContainerTable") + toggleColumns("employee-tab", "fieldContainerTable") if (!localStorage.getItem("employee_tab")) { - $("#fieldContainerTable").find("[type=checkbox]").prop("checked",true).change() - } + $("#fieldContainerTable").find("[type=checkbox]").prop("checked", true).change() + } function handleFormSubmit() { $('#successMessage').show(); - setTimeout(function() { + setTimeout(function () { $('#successMessage').hide(); }, 3000); @@ -660,36 +558,36 @@ + $(document).on('click', function (event) { + if (!$(event.target).closest('#enlargeDocContainer').length) { + hideEnlargeDoc() + } + }) + {% endblock content %} diff --git a/employee/templates/employee_export_filter.html b/employee/templates/employee_export_filter.html index ab48537e1..c9fa558f8 100644 --- a/employee/templates/employee_export_filter.html +++ b/employee/templates/employee_export_filter.html @@ -1,114 +1,107 @@ {% load i18n %} {% load static %} -
    -
    -
    {% trans "Excel columns" %}
    -
    -
    -
    -
    - -
    -
    -
    -
    - {% for field in export_form.selected_fields %} -
    -
    - -
    -
    - {% endfor %} -
    +
    +

    + {% trans "Export Employees" %} +

    + +
    +
    + {% csrf_token %} +
    +
    +
    {% trans "Excel columns" %}
    +
    +
    +
    +
    + +
    +
    +
    +
    + {% for field in export_form.selected_fields %} +
    +
    + +
    +
    + {% endfor %} +
    +
    +
    +
    +
    {% trans "Employee" %}
    +
    +
    +
    +
    + + {{export_filter.form.country}} +
    +
    +
    +
    + + {{export_filter.form.gender}} +
    +
    +
    +
    +
    +
    +
    {% trans "Work Info" %}
    +
    +
    +
    +
    + + {{export_filter.form.employee_work_info__company_id}} +
    +
    + + {{export_filter.form.employee_work_info__department_id}} +
    +
    + + {{export_filter.form.employee_work_info__shift_id}} +
    +
    +
    +
    + + {{export_filter.form.employee_work_info__reporting_manager_id}} +
    +
    + + {{export_filter.form.employee_work_info__job_position_id}} +
    +
    + + {{export_filter.form.employee_work_info__work_type_id}} +
    +
    +
    +
    +
    +
    + +
    -
    -
    -
    {% trans "Employee" %}
    -
    -
    -
    -
    - - {{export_filter.form.country}} -
    -
    -
    -
    - - {{export_filter.form.gender}} -
    -
    -
    -
    -
    -
    -
    {% trans "Work Info" %}
    -
    -
    -
    -
    - - {{export_filter.form.employee_work_info__company_id}} -
    -
    - - {{export_filter.form.employee_work_info__department_id}} -
    -
    - - {{export_filter.form.employee_work_info__shift_id}} -
    -
    -
    -
    - - {{export_filter.form.employee_work_info__reporting_manager_id}} -
    -
    - - {{export_filter.form.employee_work_info__job_position_id}} -
    -
    - - {{export_filter.form.employee_work_info__work_type_id}} -
    -
    -
    -
    -
    - diff --git a/employee/templates/employee_nav.html b/employee/templates/employee_nav.html index 0ead74620..c077d6d15 100644 --- a/employee/templates/employee_nav.html +++ b/employee/templates/employee_nav.html @@ -100,39 +100,11 @@
- diff --git a/employee/templates/policies/form.html b/employee/templates/policies/form.html index ffc0804de..ceb6296f6 100644 --- a/employee/templates/policies/form.html +++ b/employee/templates/policies/form.html @@ -1,7 +1,22 @@ {% load i18n %} -
- {{ form.as_p }} -
- -
-
+ +
+

+ {% trans "Policy" %} +

+ +
+ +
+
+ {{ form.as_p }} + +
+
diff --git a/employee/templates/policies/nav.html b/employee/templates/policies/nav.html index 55fe3d86a..a2be9f987 100644 --- a/employee/templates/policies/nav.html +++ b/employee/templates/policies/nav.html @@ -13,7 +13,7 @@ {% if perms.payroll.add_policyaccount %}
- + {% trans 'Create' %} @@ -22,13 +22,3 @@ {% endif %}
- - diff --git a/employee/templates/policies/records.html b/employee/templates/policies/records.html index fced99592..be614598a 100644 --- a/employee/templates/policies/records.html +++ b/employee/templates/policies/records.html @@ -14,7 +14,7 @@ {{ policy.title }}
{% if perms.employee.change_policiy %} - + {% endif %} {% if perms.employee.delete_policy %} @@ -24,7 +24,7 @@
{{ policy.body|safe }}
- {% trans 'View policy' %} + {% trans 'View policy' %}
{% endfor %} diff --git a/employee/templates/policies/view_policy.html b/employee/templates/policies/view_policy.html index 5defb980d..d4d084626 100644 --- a/employee/templates/policies/view_policy.html +++ b/employee/templates/policies/view_policy.html @@ -7,9 +7,20 @@ width: 200px; } -

{{ policy.title }}

- -{{ policy.body|safe }} -
+
+

+ {{ policy.title }} +

+ +
-
+
+ + {{ policy.body|safe }} +
+ +
+
diff --git a/employee/templates/tabs/allowance_deduction-tab.html b/employee/templates/tabs/allowance_deduction-tab.html index e534100e3..91b45321f 100644 --- a/employee/templates/tabs/allowance_deduction-tab.html +++ b/employee/templates/tabs/allowance_deduction-tab.html @@ -1,7 +1,7 @@ {% load i18n %} {% load static %} {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %} {% if messages %}
{% for message in messages %} diff --git a/employee/templates/tabs/attendance-tab.html b/employee/templates/tabs/attendance-tab.html index 607f08939..3f0cc5da9 100644 --- a/employee/templates/tabs/attendance-tab.html +++ b/employee/templates/tabs/attendance-tab.html @@ -1,7 +1,7 @@ {% load i18n %} {% load static %} {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %}
    @@ -170,7 +170,7 @@
    {% trans "Year" %}
    {% trans "Hour Account" %}
    {% trans "Overtime" %}
    - {% if perms.recruitment.change_attendanceovertime or perms.recruitment.delete_attendanceovertime %} + {% if perms.attendance.change_attendanceovertime or perms.attendance.delete_attendanceovertime %}
    {% trans "Actions" %}
    {% endif %}
@@ -196,7 +196,7 @@
{{ot.year}}
{{ot.worked_hours}}
{{ot.overtime}}
- {% if perms.recruitment.change_attendanceovertime or perms.recruitment.delete_attendanceovertime %} + {% if perms.attendance.change_attendanceovertime or perms.attendance.delete_attendanceovertime %}
{% if perms.attendance.change_attendanceovertime %} diff --git a/employee/templates/tabs/contract-tab.html b/employee/templates/tabs/contract-tab.html index a87b2dbb2..7b7c588e3 100644 --- a/employee/templates/tabs/contract-tab.html +++ b/employee/templates/tabs/contract-tab.html @@ -1,4 +1,4 @@ -{% load onboardingfilters %} {% load i18n %} +{% load i18n %}
-
- - - -
-
-
- {% if perms.employee.view_employee %} - - - - {% endif %} -
- {% if not 'offline_employees' in charts %} - {% if perms.employee.view_employee or request.user|is_reportingmanager %} -
- {% include "dashboard/not_in_yet.html" %} -
- {% endif %} - {% endif %} - {% if not 'online_employees' in charts %} - {% if perms.employee.view_employee or request.user|is_reportingmanager %} -
- {% include "dashboard/not_out_yet.html" %} -
- {% endif %} - {% endif %} - {% if not 'overall_leave_chart' in charts %} - {% if perms.leave.view_leaverequest %} -
-
-
- {% trans "Overall Leave" %} - - close - - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if perms.recruitment.view_candidate or request.user|is_stagemanager %} - {% if not 'hired_candidates' in charts %} -
-
-
- {% trans "Hired Candidates" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'onboarding_candidates' in charts %} -
-
-
- {% trans "Candidates Started Onboarding" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'recruitment_analytics' in charts %} - {% if request.user|is_stagemanager or perms.recruitment.view_recruitment %} -
-
-
- {% trans "Recruitment Analytics" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'attendance_analytic' in charts %} - {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} -
-
-
-
- {% trans "Attendance Analytics" %} - - close - -
-
- - - - -
-
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'hours_chart' in charts %} - {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} -
-
-
-
- {% trans "Hours Chart" %} - - close - -
-
- -
-
- -
-
-
-
- {% endif %} - {% endif %} - {% if not 'employees_chart' in charts %} -
-
-
- {% trans "Employees Chart" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'department_chart' in charts %} -
-
-
- {% trans "Department Chart" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'gender_chart' in charts %} -
-
-
- {% trans "Gender Chart" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'objective_status' in charts %} - {% if perms.pms.view_employeeobjective or request.user|is_reportingmanager %} -
-
-
- {% trans "Objective Status" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'key_result_status' in charts %} - {% if perms.pms.view_employeekeyresult or request.user|is_reportingmanager %} -
-
-
- {% trans "Key Result Status" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'feedback_status' in charts %} - {% if perms.pms.view_feedback or request.user|is_reportingmanager %} -
-
-
- {% trans "Feedback Status" %} - - close - -
-
-
- -
-
-
-
- {% endif %} - {% endif %} - {% if not 'shift_request_approve' in charts %} - {% if perms.base.change_shiftrequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Shift Requests To Approve" %} - - close - -
-
- {% include "request_and_approve/shift_request.html" %} -
-
-
- {% endif %} - {% endif %} - {% if not 'work_type_request_approve' in charts %} - {% if perms.base.change_worktyperequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Work Type Requests To Approve" %} - - close - -
-
- {% include "request_and_approve/work_type_request.html" %} -
-
-
- {% endif %} - {% endif %} - {% if not 'overtime_approve' in charts %} - {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} -
-
- {% include "request_and_approve/overtime_approve.html" %} -
-
- {% endif %} - {% endif %} - {% if not 'attendance_validate' in charts %} - {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} -
-
- {% include "request_and_approve/attendance_validate.html" %} -
-
- {% endif %} - {% endif %} - {% if not 'leave_request_approve' in charts %} - {% if perms.leave.change_leaverequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Leave Requests To Approve" %} - - close - -
-
-
-
-
- {% endif %} - {% endif %} - {% if not 'leave_allocation_approve' in charts %} - {% if perms.leave.change_leaveallocationrequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Leave Allocation Request To Approve" %} - - close - -
-
- {% include "request_and_approve/leave_request_approve.html" %} -
-
-
- {% endif %} - {% endif %} - {% if not 'feedback_answer' in charts %} -
-
-
- {% trans "Feedback To Answers" %} - - close - -
-
- {% include "request_and_approve/feedback_answer.html" %} -
-
-
- {% endif %} - {% if not 'asset_request_approve' in charts %} - {% if perms.asset.change_assetrequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Asset Requests To Approve" %} - - close - -
-
- {% include "request_and_approve/asset_requests_approve.html" %} -
-
-
- {% endif %} - {% endif %} -
-
-
-
-
-
-
    -
+
+ + + +
+
+
+ {% if perms.employee.view_employee %} + {% if "recruitment"|app_installed %} + + + {% endif %} + + {% endif %} +
+ {% if "attendance"|app_installed %} + {% if not 'offline_employees' in charts %} + {% if perms.employee.view_employee or request.user|is_reportingmanager %} +
+ {% include "dashboard/not_in_yet.html" %} +
+ {% endif %} + {% endif %} + {% if not 'online_employees' in charts %} + {% if perms.employee.view_employee or request.user|is_reportingmanager %} +
+ {% include "dashboard/not_out_yet.html" %} +
+ {% endif %} + {% endif %} + {% endif %} + {% if "leave"|app_installed and not 'overall_leave_chart' in charts %} + {% if perms.leave.view_leaverequest %} +
+
+
+ {% trans "Overall Leave" %} + + close + + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "recruitment"|app_installed and perms.recruitment.view_candidate or request.user|is_stagemanager %} + {% if not 'hired_candidates' in charts %} +
+
+
+ {% trans "Hired Candidates" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if "onboarding"|app_installed and not 'onboarding_candidates' in charts %} +
+
+
+ {% trans "Candidates Started Onboarding" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "recruitment"|app_installed and not 'recruitment_analytics' in charts %} + {% if request.user|is_stagemanager or perms.recruitment.view_recruitment %} +
+
+
+ {% trans "Recruitment Analytics" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed and not 'attendance_analytic' in charts %} + {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} +
+
+
+
+ {% trans "Attendance Analytics" %} + + close + +
+
+ + + + +
+
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed and not 'hours_chart' in charts %} + {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} +
+
+
+
+ {% trans "Hours Chart" %} + + close + +
+
+ +
+
+ +
+
+
+
+ {% endif %} + {% endif %} + {% if not 'employees_chart' in charts %} +
+
+
+ {% trans "Employees Chart" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if not 'department_chart' in charts %} +
+
+
+ {% trans "Department Chart" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if not 'gender_chart' in charts %} +
+
+
+ {% trans "Gender Chart" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if "pms"|app_installed and not 'objective_status' in charts %} + {% if perms.pms.view_employeeobjective or request.user|is_reportingmanager %} +
+
+
+ {% trans "Objective Status" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "pms"|app_installed and not 'key_result_status' in charts %} + {% if perms.pms.view_employeekeyresult or request.user|is_reportingmanager %} +
+
+
+ {% trans "Key Result Status" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "pms"|app_installed and not 'feedback_status' in charts %} + {% if perms.pms.view_feedback or request.user|is_reportingmanager %} +
+
+
+ {% trans "Feedback Status" %} + + close + +
+
+
+ +
+
+
+
+ {% endif %} + {% endif %} + {% if not 'shift_request_approve' in charts %} + {% if perms.base.change_shiftrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Shift Requests To Approve" %} + + close + +
+
+ {% include "request_and_approve/shift_request.html" %} +
+
+
+ {% endif %} + {% endif %} + {% if not 'work_type_request_approve' in charts %} + {% if perms.base.change_worktyperequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Work Type Requests To Approve" %} + + close + +
+
+ {% include "request_and_approve/work_type_request.html" %} +
+
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed and not 'overtime_approve' in charts %} + {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} +
+
+ {% include "request_and_approve/overtime_approve.html" %} +
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed %} + {% if not 'attendance_validate' in charts %} + {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} +
+
+ {% include "request_and_approve/attendance_validate.html" %} +
+
+ {% endif %} + {% endif %} + {% endif %} + {% if "leave"|app_installed and not 'leave_request_approve' in charts %} + {% if perms.leave.change_leaverequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Leave Requests To Approve" %} + + close + +
+
+
+
+
+ {% endif %} + {% endif %} + {% if "leave"|app_installed and not 'leave_allocation_approve' in charts %} + {% if perms.leave.change_leaveallocationrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Leave Allocation Request To Approve" %} + + close + +
+
+ {% include "request_and_approve/leave_request_approve.html" %} +
+
+
+ {% endif %} + {% endif %} + {% if "pms"|app_installed and not 'feedback_answer' in charts %} +
+
+
+ {% trans "Feedback To Answers" %} + + close + +
+
+ {% include "request_and_approve/feedback_answer.html" %} +
+
+
+ {% endif %} + {% if "asset"|app_installed and not 'asset_request_approve' in charts %} + {% if perms.asset.change_assetrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Asset Requests To Approve" %} + + close + +
+
+ {% include "request_and_approve/asset_requests_approve.html" %} +
+
+
+ {% endif %} + {% endif %} +
+
+
+
+
+
+
    +
+
-
-
- {% trans "Announcements" %} - {% if perms.base.add_announcement %} - - - - {% endif %} -
- -
- -
-
-
-
- - {% if not announcement %} -
-
- Page not found. 404. -
{% trans "No Announcements to show." %}
-
+ height: 28px;" class="oh-btn oh-btn--secondary-outline float-end ms-3" + hx-get='{% url "create-announcement" %}' hx-target="#objectCreateModalTarget" + hx-swap="innerHTML" data-toggle="oh-modal-toggle" data-target="#objectCreateModal" + title='{% trans "Create Announcement." %}'> + + + + {% endif %}
- {% else %} - {% for i in announcement %} -
- + +
+ +
+
+
+
+ + {% if not announcement %} +
+
+ Page not found. 404. +
{% trans "No Announcements to show." %}
+
+
+ {% else %} + {% for i in announcement %} + + {% endfor %} + {% endif %} +
+
- {% endfor %} - {% endif %} +
+
+ + {% if "leave"|app_installed %} +
+
+ {% trans "On Leave" %} +
+
+
+ {% endif %} +
+ +
+ {% trans "Employee Work Information" %} +
+ + + + + {% if request.user.employee_get.employee_user_id.is_superuser or request.user|is_reportingmanager %} +
+
+ {% endif %} +
-
-
- -
-
- {% trans "On Leave" %} -
-
-
-
-
- -
- {% trans "Employee Work Information" %} -
- - - - - {% if request.user.employee_get.employee_user_id.is_superuser or request.user|is_reportingmanager %} -
-
- {% endif %} - -
-
-
- - - - - -
@@ -936,110 +747,127 @@ - -{% if perms.recruitment.view_recruitment or request.user|is_stagemanager %} - - -{% endif %} {% if perms.employee.view_employee or request.user|is_reportingmanager %} -{% endif %} {% if perms.employee.view_attendance or request.user|is_reportingmanager %} - - - - - +{% if "leave"|app_installed %} + +{% endif %} + +{% if "leave"|app_installed %} + +{% endif %} + +{% if "recruitment"|app_installed %} + {% if perms.recruitment.view_recruitment or request.user|is_stagemanager %} + + {% endif %} +{% endif %} + +{% if "attendance"|app_installed %} + {% if perms.attendance.view_attendance or request.user|is_reportingmanager %} + + {% endif %} +{% endif %} + +{% if "onboarding"|app_installed %} + + +{% endif %} + +{% if "pms"|app_installed %} + + {% endif %} {% if not request.user.driverviewed_set.first or "dashboard" not in request.user.driverviewed_set.first.user_viewed %} -{% endif %} +{% endif %} diff --git a/templates/floating_button.html b/templates/floating_button.html index 5ae47b9ac..bd753404b 100644 --- a/templates/floating_button.html +++ b/templates/floating_button.html @@ -1,4 +1,5 @@ {% load static %} +{% load horillafilters %}