wbhuman_resources 1.58.4__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. wbhuman_resources/__init__.py +1 -0
  2. wbhuman_resources/admin/__init__.py +5 -0
  3. wbhuman_resources/admin/absence.py +113 -0
  4. wbhuman_resources/admin/calendars.py +37 -0
  5. wbhuman_resources/admin/employee.py +109 -0
  6. wbhuman_resources/admin/kpi.py +21 -0
  7. wbhuman_resources/admin/review.py +157 -0
  8. wbhuman_resources/apps.py +23 -0
  9. wbhuman_resources/dynamic_preferences_registry.py +119 -0
  10. wbhuman_resources/factories/__init__.py +38 -0
  11. wbhuman_resources/factories/absence.py +109 -0
  12. wbhuman_resources/factories/calendars.py +60 -0
  13. wbhuman_resources/factories/employee.py +80 -0
  14. wbhuman_resources/factories/kpi.py +155 -0
  15. wbhuman_resources/filters/__init__.py +20 -0
  16. wbhuman_resources/filters/absence.py +109 -0
  17. wbhuman_resources/filters/absence_graphs.py +85 -0
  18. wbhuman_resources/filters/calendars.py +28 -0
  19. wbhuman_resources/filters/employee.py +81 -0
  20. wbhuman_resources/filters/kpi.py +35 -0
  21. wbhuman_resources/filters/review.py +134 -0
  22. wbhuman_resources/filters/signals.py +27 -0
  23. wbhuman_resources/locale/de/LC_MESSAGES/django.mo +0 -0
  24. wbhuman_resources/locale/de/LC_MESSAGES/django.po +2207 -0
  25. wbhuman_resources/locale/de/LC_MESSAGES/django.po.translated +2456 -0
  26. wbhuman_resources/locale/en/LC_MESSAGES/django.mo +0 -0
  27. wbhuman_resources/locale/en/LC_MESSAGES/django.po +2091 -0
  28. wbhuman_resources/locale/fr/LC_MESSAGES/django.mo +0 -0
  29. wbhuman_resources/locale/fr/LC_MESSAGES/django.po +2093 -0
  30. wbhuman_resources/management/__init__.py +23 -0
  31. wbhuman_resources/migrations/0001_initial_squashed_squashed_0015_alter_absencerequest_calendaritem_ptr_and_more.py +949 -0
  32. wbhuman_resources/migrations/0016_alter_employeehumanresource_options.py +20 -0
  33. wbhuman_resources/migrations/0017_absencerequest_crossborder_country_and_more.py +55 -0
  34. wbhuman_resources/migrations/0018_remove_position_group_position_groups.py +32 -0
  35. wbhuman_resources/migrations/0019_alter_absencerequest_options_alter_kpi_options_and_more.py +44 -0
  36. wbhuman_resources/migrations/0020_alter_employeeyearbalance_year_alter_review_year.py +27 -0
  37. wbhuman_resources/migrations/0021_alter_position_color.py +18 -0
  38. wbhuman_resources/migrations/0022_remove_review_editable_mode.py +64 -0
  39. wbhuman_resources/migrations/__init__.py +0 -0
  40. wbhuman_resources/models/__init__.py +23 -0
  41. wbhuman_resources/models/absence.py +903 -0
  42. wbhuman_resources/models/calendars.py +370 -0
  43. wbhuman_resources/models/employee.py +1241 -0
  44. wbhuman_resources/models/kpi.py +199 -0
  45. wbhuman_resources/models/preferences.py +40 -0
  46. wbhuman_resources/models/review.py +982 -0
  47. wbhuman_resources/permissions/__init__.py +0 -0
  48. wbhuman_resources/permissions/backend.py +26 -0
  49. wbhuman_resources/serializers/__init__.py +49 -0
  50. wbhuman_resources/serializers/absence.py +308 -0
  51. wbhuman_resources/serializers/calendars.py +73 -0
  52. wbhuman_resources/serializers/employee.py +267 -0
  53. wbhuman_resources/serializers/kpi.py +80 -0
  54. wbhuman_resources/serializers/review.py +415 -0
  55. wbhuman_resources/signals.py +4 -0
  56. wbhuman_resources/tasks.py +195 -0
  57. wbhuman_resources/templates/review/review_report.html +322 -0
  58. wbhuman_resources/tests/__init__.py +1 -0
  59. wbhuman_resources/tests/conftest.py +96 -0
  60. wbhuman_resources/tests/models/__init__.py +0 -0
  61. wbhuman_resources/tests/models/test_absences.py +478 -0
  62. wbhuman_resources/tests/models/test_calendars.py +209 -0
  63. wbhuman_resources/tests/models/test_employees.py +502 -0
  64. wbhuman_resources/tests/models/test_review.py +103 -0
  65. wbhuman_resources/tests/models/test_utils.py +110 -0
  66. wbhuman_resources/tests/signals.py +108 -0
  67. wbhuman_resources/tests/test_permission.py +64 -0
  68. wbhuman_resources/tests/test_tasks.py +74 -0
  69. wbhuman_resources/urls.py +221 -0
  70. wbhuman_resources/utils.py +43 -0
  71. wbhuman_resources/viewsets/__init__.py +61 -0
  72. wbhuman_resources/viewsets/absence.py +312 -0
  73. wbhuman_resources/viewsets/absence_charts.py +328 -0
  74. wbhuman_resources/viewsets/buttons/__init__.py +7 -0
  75. wbhuman_resources/viewsets/buttons/absence.py +32 -0
  76. wbhuman_resources/viewsets/buttons/employee.py +44 -0
  77. wbhuman_resources/viewsets/buttons/kpis.py +16 -0
  78. wbhuman_resources/viewsets/buttons/review.py +195 -0
  79. wbhuman_resources/viewsets/calendars.py +103 -0
  80. wbhuman_resources/viewsets/display/__init__.py +39 -0
  81. wbhuman_resources/viewsets/display/absence.py +334 -0
  82. wbhuman_resources/viewsets/display/calendars.py +83 -0
  83. wbhuman_resources/viewsets/display/employee.py +254 -0
  84. wbhuman_resources/viewsets/display/kpis.py +92 -0
  85. wbhuman_resources/viewsets/display/review.py +429 -0
  86. wbhuman_resources/viewsets/employee.py +210 -0
  87. wbhuman_resources/viewsets/endpoints/__init__.py +42 -0
  88. wbhuman_resources/viewsets/endpoints/absence.py +57 -0
  89. wbhuman_resources/viewsets/endpoints/calendars.py +18 -0
  90. wbhuman_resources/viewsets/endpoints/employee.py +51 -0
  91. wbhuman_resources/viewsets/endpoints/kpis.py +53 -0
  92. wbhuman_resources/viewsets/endpoints/review.py +191 -0
  93. wbhuman_resources/viewsets/kpi.py +280 -0
  94. wbhuman_resources/viewsets/menu/__init__.py +22 -0
  95. wbhuman_resources/viewsets/menu/absence.py +50 -0
  96. wbhuman_resources/viewsets/menu/administration.py +15 -0
  97. wbhuman_resources/viewsets/menu/calendars.py +33 -0
  98. wbhuman_resources/viewsets/menu/employee.py +44 -0
  99. wbhuman_resources/viewsets/menu/kpis.py +18 -0
  100. wbhuman_resources/viewsets/menu/review.py +97 -0
  101. wbhuman_resources/viewsets/mixins.py +14 -0
  102. wbhuman_resources/viewsets/review.py +837 -0
  103. wbhuman_resources/viewsets/titles/__init__.py +18 -0
  104. wbhuman_resources/viewsets/titles/absence.py +30 -0
  105. wbhuman_resources/viewsets/titles/employee.py +18 -0
  106. wbhuman_resources/viewsets/titles/kpis.py +15 -0
  107. wbhuman_resources/viewsets/titles/review.py +62 -0
  108. wbhuman_resources/viewsets/utils.py +28 -0
  109. wbhuman_resources-1.58.4.dist-info/METADATA +8 -0
  110. wbhuman_resources-1.58.4.dist-info/RECORD +111 -0
  111. wbhuman_resources-1.58.4.dist-info/WHEEL +5 -0
@@ -0,0 +1,110 @@
1
+ from datetime import date, timedelta
2
+
3
+ import pandas as pd
4
+ import pytest
5
+ from faker import Faker
6
+
7
+ from wbhuman_resources.models import EmployeeHumanResource
8
+
9
+ fake = Faker()
10
+
11
+
12
+ @pytest.mark.django_db
13
+ class TestUtil:
14
+ @pytest.mark.parametrize("test_date", [(fake.date_object())])
15
+ def test_get_employee_absence_periods_df(
16
+ self,
17
+ employee_human_resource,
18
+ absence_request_periods_factory,
19
+ day_off_factory,
20
+ employee_weekly_off_periods_factory,
21
+ test_date,
22
+ ):
23
+ # Basic utility coverage test. The underlying functions are already tested.
24
+ p = absence_request_periods_factory.create(date=test_date, employee=employee_human_resource)
25
+ w = employee_weekly_off_periods_factory.create(
26
+ employee=employee_human_resource, weekday=(test_date + timedelta(days=1)).weekday()
27
+ )
28
+ day_off_factory.create(calendar=employee_human_resource.calendar, date=test_date + timedelta(days=2))
29
+ res = EmployeeHumanResource.get_employee_absence_periods_df(
30
+ employee_human_resource.calendar,
31
+ (test_date - pd.tseries.offsets.Week(1)).date(),
32
+ (test_date + pd.tseries.offsets.Week(1)).date(),
33
+ EmployeeHumanResource.active_employees.all(),
34
+ ).set_index(["employee", "period", "date", "type"])
35
+ assert not res.loc[(employee_human_resource.id, p.default_period.id, test_date, "Vacation"), :].empty
36
+ assert not res.loc[
37
+ (employee_human_resource.id, w.period.id, test_date + timedelta(days=1), "Day Off"), :
38
+ ].empty
39
+ assert not res.loc[
40
+ (
41
+ employee_human_resource.id,
42
+ employee_human_resource.calendar.default_periods.earliest("lower_time").id,
43
+ test_date + timedelta(days=2),
44
+ "Holiday",
45
+ ),
46
+ :,
47
+ ].empty
48
+ assert not res.loc[
49
+ (
50
+ employee_human_resource.id,
51
+ employee_human_resource.calendar.default_periods.latest("lower_time").id,
52
+ test_date + timedelta(days=2),
53
+ "Holiday",
54
+ ),
55
+ :,
56
+ ].empty
57
+ with pytest.raises(KeyError):
58
+ assert not res.loc[(employee_human_resource.id, 2, test_date + timedelta(days=3), "Holiday"), :].empty
59
+
60
+ @pytest.mark.parametrize(
61
+ "end_of_month_date",
62
+ [
63
+ (fake.date_between(date(2021, 1, 1), date(2021, 3, 31))),
64
+ (fake.date_between(date(2021, 4, 1), date(2021, 12, 31))),
65
+ ],
66
+ )
67
+ def test_get_end_of_month_employee_balance_report_df(
68
+ self,
69
+ employee_human_resource,
70
+ employee_year_balance_factory,
71
+ balance_hourly_allowance_factory,
72
+ absence_request_periods_factory,
73
+ end_of_month_date,
74
+ ):
75
+ end_of_month_date = (end_of_month_date + pd.tseries.offsets.MonthEnd(0)).date()
76
+ previous_balance = employee_year_balance_factory.create(
77
+ employee=employee_human_resource, year=end_of_month_date.year - 1, extra_balance=0
78
+ )
79
+ balance_hourly_allowance_factory.create(balance=previous_balance)
80
+
81
+ current_balance = employee_year_balance_factory.create(
82
+ employee=employee_human_resource, year=end_of_month_date.year, extra_balance=0
83
+ )
84
+ balance_hourly_allowance_factory.create(balance=current_balance)
85
+ previous_period = absence_request_periods_factory.create(
86
+ balance=previous_balance,
87
+ employee=employee_human_resource,
88
+ date=fake.date_between(date(end_of_month_date.year, 1, 1), end_of_month_date - timedelta(days=1)),
89
+ )
90
+ current_period = absence_request_periods_factory.create(
91
+ balance=current_balance,
92
+ employee=employee_human_resource,
93
+ date=fake.date_between(previous_period.date, end_of_month_date),
94
+ )
95
+
96
+ res = EmployeeHumanResource.get_end_of_month_employee_balance_report_df(
97
+ EmployeeHumanResource.active_employees.all(), end_of_month_date
98
+ ).to_dict("records")[0]
99
+ expected_total_balance = (
100
+ current_balance.balance_in_days
101
+ + previous_balance.total_vacation_hourly_balance_in_days
102
+ + previous_balance.total_vacation_hourly_usage_in_days
103
+ )
104
+ expected_usage = (previous_period.total_hours + current_period.total_hours) / 8
105
+ assert res == {
106
+ "Employee": employee_human_resource.computed_str,
107
+ "Total Balance": expected_total_balance,
108
+ "End of Month Usage": expected_usage,
109
+ "Available Balance End of Month": expected_total_balance - expected_usage,
110
+ }
@@ -0,0 +1,108 @@
1
+ from django.db import connection
2
+ from django.dispatch import receiver
3
+ from wbcore.test.signals import (
4
+ custom_update_kwargs,
5
+ get_custom_factory,
6
+ get_custom_serializer,
7
+ )
8
+
9
+ from wbhuman_resources.factories import (
10
+ DefaultPersonKPIFactory,
11
+ ReviewQuestionNoCategoryFactory,
12
+ ReviewTemplateFactory,
13
+ )
14
+ from wbhuman_resources.models import AbsenceRequest, ReviewQuestion
15
+ from wbhuman_resources.serializers import KPIModelSerializer
16
+ from wbhuman_resources.viewsets import (
17
+ AbsenceTablePandasViewSet,
18
+ AbsenceTypeCountEmployeeModelViewSet,
19
+ KPIModelViewSet,
20
+ ReviewAnswerReviewNoCategoryModelViewSet,
21
+ ReviewAnswerReviewPandasViewSet,
22
+ ReviewAnswerReviewQuestionCategoryModelViewSet,
23
+ ReviewModelViewSet,
24
+ ReviewProgressReviewPandasViewSet,
25
+ ReviewQuestionReviewNoCategoryModelViewSet,
26
+ ReviewReviewGroupModelViewSet,
27
+ ReviewTemplateModelViewSet,
28
+ )
29
+
30
+ # =================================================================================================================
31
+ # CUSTOM FACTORY
32
+ # =================================================================================================================
33
+
34
+
35
+ @receiver(get_custom_factory, sender=ReviewTemplateModelViewSet)
36
+ def receive_factory_review_template(sender, *args, **kwargs):
37
+ return ReviewTemplateFactory
38
+
39
+
40
+ @receiver(get_custom_factory, sender=ReviewQuestionReviewNoCategoryModelViewSet)
41
+ def receive_factory_review_question_no_category(sender, *args, **kwargs):
42
+ return ReviewQuestionNoCategoryFactory
43
+
44
+
45
+ @receiver(get_custom_factory, sender=KPIModelViewSet)
46
+ def receive_factory_kpi(sender, *args, **kwargs):
47
+ return DefaultPersonKPIFactory
48
+
49
+
50
+ # =================================================================================================================
51
+ # UPDATE KWARGS
52
+ # =================================================================================================================
53
+
54
+
55
+ @receiver(custom_update_kwargs, sender=AbsenceTypeCountEmployeeModelViewSet)
56
+ def receive_kwargs_employee_absence(sender, *args, **kwargs):
57
+ if kwargs.get("obj_factory"):
58
+ abs_day = kwargs.get("obj_factory")
59
+ abs_day.request.status = AbsenceRequest.Status.APPROVED
60
+ abs_day.request.save()
61
+ abs_day.save()
62
+ return {"employee_id": abs_day.request.employee.id, "request_id": abs_day.request.id}
63
+ else:
64
+ return {}
65
+
66
+
67
+ @receiver(custom_update_kwargs, sender=ReviewTemplateModelViewSet)
68
+ @receiver(custom_update_kwargs, sender=ReviewReviewGroupModelViewSet)
69
+ @receiver(custom_update_kwargs, sender=ReviewModelViewSet)
70
+ def receive_kwargs_review(sender, *args, **kwargs):
71
+ if (review := kwargs.get("obj_factory")) and (user := kwargs.get("user")):
72
+ review.moderator = user.profile
73
+ review.save()
74
+ return {}
75
+
76
+
77
+ @receiver(custom_update_kwargs, sender=ReviewProgressReviewPandasViewSet)
78
+ @receiver(custom_update_kwargs, sender=ReviewAnswerReviewPandasViewSet)
79
+ @receiver(custom_update_kwargs, sender=ReviewAnswerReviewNoCategoryModelViewSet)
80
+ @receiver(custom_update_kwargs, sender=ReviewAnswerReviewQuestionCategoryModelViewSet)
81
+ def receive_kwargs_review_answer_question_category(sender, *args, **kwargs):
82
+ if question_id := kwargs.get("question_id"):
83
+ question = ReviewQuestion.objects.get(id=question_id)
84
+ if question.category:
85
+ return {"review_id": question.review.id, "category_id": question.category.id}
86
+ else:
87
+ return {"review_id": question.review.id}
88
+ return {}
89
+
90
+
91
+ @receiver(custom_update_kwargs, sender=AbsenceTablePandasViewSet)
92
+ def receive_kwargs_employee_human_resource_absence(sender, *args, **kwargs):
93
+ return {"request_id": None, "profile": None, "user": None, "obj_factory": None}
94
+
95
+
96
+ # =================================================================================================================
97
+ # UPDATE SERIALIZER
98
+ # =================================================================================================================
99
+
100
+
101
+ @receiver(get_custom_serializer, sender=KPIModelViewSet)
102
+ def receive_serializer_kpi(sender, *args, **kwargs):
103
+ return KPIModelSerializer
104
+
105
+
106
+ def app_pre_migration(sender, app_config, **kwargs):
107
+ cur = connection.cursor()
108
+ cur.execute("CREATE EXTENSION IF NOT EXISTS btree_gist;")
@@ -0,0 +1,64 @@
1
+ import pytest
2
+ from django.contrib.auth import get_user_model
3
+ from dynamic_preferences.registries import global_preferences_registry
4
+ from faker import Faker
5
+ from rest_framework.test import APIRequestFactory
6
+ from wbcore.contrib.authentication.factories import UserFactory
7
+ from wbcore.permissions.shortcuts import is_internal_user
8
+
9
+ from wbhuman_resources.factories.employee import EmployeeHumanResourceFactory
10
+ from wbhuman_resources.models.employee import EmployeeHumanResource
11
+
12
+ User = get_user_model()
13
+ fake = Faker()
14
+
15
+
16
+ @pytest.mark.django_db
17
+ class TestPermissionTasks:
18
+ @pytest.fixture
19
+ def request_user_external(self):
20
+ request = APIRequestFactory()
21
+ user = UserFactory()
22
+ EmployeeHumanResourceFactory.create(
23
+ profile=user.profile, contract_type=EmployeeHumanResource.ContractType.EXTERNAL
24
+ )
25
+ request.user = user
26
+ return request
27
+
28
+ @pytest.fixture
29
+ def request_user_active_internal(self):
30
+ request = APIRequestFactory()
31
+ user = UserFactory()
32
+ EmployeeHumanResourceFactory.create(
33
+ profile=user.profile, contract_type=EmployeeHumanResource.ContractType.INTERNAL
34
+ )
35
+ request.user = user
36
+ return request
37
+
38
+ @pytest.fixture
39
+ def request_user_inactive_internal(self):
40
+ request = APIRequestFactory()
41
+ user = UserFactory()
42
+ EmployeeHumanResourceFactory.create(
43
+ profile=user.profile, contract_type=EmployeeHumanResource.ContractType.INTERNAL, is_active=False
44
+ )
45
+ request.user = user
46
+ return request
47
+
48
+ def test_permission_active_internal(self, request_user_active_internal):
49
+ assert is_internal_user(request_user_active_internal.user) is True
50
+
51
+ def test_permission_inactive_internal(self, request_user_inactive_internal):
52
+ assert is_internal_user(request_user_inactive_internal.user) is False
53
+
54
+ def test_permission_external(self, request_user_external):
55
+ assert is_internal_user(request_user_external.user) is False
56
+
57
+ def test_permission_external_but_considered_internal(self, request_user_external):
58
+ from wbcore.permissions.registry import user_registry
59
+
60
+ user = request_user_external.user
61
+ global_preferences_registry.manager()["wbhuman_resources__is_external_considered_as_internal"] = True
62
+ user = User.objects.get(id=user.id) # reload to reset cached property
63
+ user_registry.reset_cache()
64
+ assert is_internal_user(user) is True
@@ -0,0 +1,74 @@
1
+ from datetime import date
2
+ from unittest.mock import patch
3
+
4
+ import pandas as pd
5
+ import pytest
6
+ from django.contrib.auth.models import Permission
7
+ from django.contrib.contenttypes.models import ContentType
8
+ from django.core import mail
9
+ from dynamic_preferences.registries import global_preferences_registry
10
+ from faker import Faker
11
+
12
+ from wbhuman_resources.models import AbsenceRequest
13
+ from wbhuman_resources.models.employee import EmployeeHumanResource
14
+ from wbhuman_resources.tasks import assign_balance, send_mail_to_accounting
15
+
16
+ fake = Faker()
17
+
18
+
19
+ @pytest.mark.django_db
20
+ class TestHumanResourceTasks:
21
+ @pytest.mark.parametrize("accounting_email_1,accounting_email_2", [(fake.email(), fake.email())])
22
+ @patch.object(EmployeeHumanResource, "get_end_of_month_employee_balance_report_df")
23
+ def test_send_mail_to_accounting_recipient(self, mock_fct, user_factory, accounting_email_1, accounting_email_2):
24
+ global_preferences_registry.manager()["wbhuman_resources__accounting_company_emails"] = (
25
+ f"{accounting_email_1};{accounting_email_2}"
26
+ )
27
+ mock_fct.return_value = pd.DataFrame(data=["a", "b"])
28
+
29
+ admin_user = user_factory.create(is_superuser=True)
30
+ content_type = ContentType.objects.get_for_model(AbsenceRequest)
31
+ permission = Permission.objects.get(
32
+ codename="administrate_absencerequest",
33
+ content_type=content_type,
34
+ )
35
+ admin_user.user_permissions.add(permission)
36
+ user_factory.create(
37
+ is_superuser=True
38
+ ) # we create another superuser to check if administirators don't contain it
39
+
40
+ send_mail_to_accounting()
41
+ assert len(mail.outbox) == 1
42
+ last_mail = mail.outbox[-1]
43
+
44
+ assert set(last_mail.to) == {accounting_email_2, accounting_email_1}
45
+ assert set(last_mail.cc) == {admin_user.email}
46
+
47
+ @pytest.mark.parametrize("year_str", [(fake.year())])
48
+ def test_assign_yearly_balance(self, year_str, employee_human_resource_factory):
49
+ year = int(year_str)
50
+ test_date = date(year, 1, 1)
51
+ active_employee = employee_human_resource_factory.create(
52
+ enrollment_at=test_date,
53
+ is_active=True,
54
+ occupancy_rate=1,
55
+ extra_days_frequency=EmployeeHumanResource.ExtraDaysBalanceFrequency.YEARLY,
56
+ )
57
+ unactive_employee = employee_human_resource_factory.create(
58
+ enrollment_at=test_date,
59
+ is_active=False,
60
+ occupancy_rate=1,
61
+ extra_days_frequency=EmployeeHumanResource.ExtraDaysBalanceFrequency.YEARLY,
62
+ )
63
+ assert not active_employee.balances.exists()
64
+ assign_balance(test_date)
65
+ assert not unactive_employee.balances.exists()
66
+ assert active_employee.balances.count() == 1 # check that we get only this year balance
67
+ employee_balance = active_employee.balances.get(year=test_date.year)
68
+ allowance = employee_balance.monthly_allowances.first()
69
+ assert (
70
+ employee_balance.monthly_allowances.count() == 1
71
+ ) # check that a unique allowance was granted to the employee
72
+ assert (
73
+ allowance.hourly_allowance == 25 * 8
74
+ ) # check the the hourly amount correspond ot the base 25 days per year in hours
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from django.urls import include, path
4
+ from wbcore.routers import WBCoreRouter
5
+
6
+ from wbhuman_resources import viewsets
7
+
8
+ router = WBCoreRouter()
9
+
10
+
11
+ # Calendar viewsets
12
+
13
+ router.register(
14
+ "defaultdailyperiodrepresentation",
15
+ viewsets.DefaultDailyPeriodRepresentationViewSet,
16
+ basename="defaultdailyperiodrepresentation",
17
+ )
18
+ router.register(
19
+ "employeeweeklyoffperiodrepresentation",
20
+ viewsets.EmployeeWeeklyOffPeriodsRepresentationViewSet,
21
+ basename="employeeweeklyoffperiodrepresentation",
22
+ )
23
+ router.register("dayoffrepresentation", viewsets.DayOffRepresentationViewSet, basename="dayoffrepresentation")
24
+ router.register(
25
+ "dayoffcalendarrepresentation",
26
+ viewsets.DayOffCalendarRepresentationViewSet,
27
+ basename="dayoffcalendarrepresentation",
28
+ )
29
+
30
+ router.register("dayoffcalendar", viewsets.DayOffCalendarModelViewSet, basename="dayoffcalendar")
31
+ router.register("dayoff", viewsets.DayOffModelViewSet, basename="dayoff")
32
+
33
+ calendar_router = WBCoreRouter()
34
+ calendar_router.register("dayoff", viewsets.DayOffDayOffCalendarModelViewSet, basename="calendar-dayoff")
35
+ calendar_router.register(
36
+ "defaultperiod", viewsets.DefaultDailyPeriodDayOffCalendarModelViewSet, basename="calendar-defaultperiod"
37
+ )
38
+
39
+ # absence request type router
40
+ absencerequesttype_router = WBCoreRouter()
41
+ absencerequesttype_router.register(
42
+ "crossbordercountry",
43
+ viewsets.AbsenceRequestCrossBorderCountryModelViewSet,
44
+ basename="absencerequesttype-crossbordercountry",
45
+ )
46
+
47
+
48
+ # Employee routers
49
+ router.register(
50
+ r"employeehumanresourcerepresentation",
51
+ viewsets.EmployeeHumanResourceRepresentationViewSet,
52
+ basename="employeehumanresourcerepresentation",
53
+ )
54
+ router.register(r"positionrepresentation", viewsets.PositionRepresentationViewSet, basename="positionrepresentation")
55
+ router.register(
56
+ "employeeyearbalancerepresentation",
57
+ viewsets.EmployeeYearBalanceRepresentationViewSet,
58
+ basename="employeeyearbalancerepresentation",
59
+ )
60
+
61
+ router.register(r"employeebalance", viewsets.EmployeeBalanceModelViewSet, basename="employeebalance")
62
+ router.register(r"employee", viewsets.EmployeeModelViewSet, basename="employee")
63
+ router.register(r"position", viewsets.PositionModelViewSet, basename="position")
64
+
65
+
66
+ employee_router = WBCoreRouter()
67
+ employee_router.register(
68
+ r"weeklyoffperiod",
69
+ viewsets.WeeklyOffPeriodEmployeeHumanResourceModelViewSet,
70
+ basename="employee-weeklyoffperiod",
71
+ )
72
+ employee_router.register(
73
+ r"absence",
74
+ viewsets.AbsenceTypeCountEmployeeModelViewSet,
75
+ basename="employee-absencecount",
76
+ )
77
+
78
+ employee_router.register(
79
+ r"absencerequest",
80
+ viewsets.AbsenceRequestEmployeeHumanResourceModelViewset,
81
+ basename="employee-absencerequest",
82
+ )
83
+ employee_router.register(
84
+ r"employeeyearbalance",
85
+ viewsets.YearBalanceEmployeeHumanResourceModelViewset,
86
+ basename="employee-employeeyearbalance",
87
+ )
88
+
89
+ # Absence routers
90
+ router.register(
91
+ r"absencerequesttyperepresentation",
92
+ viewsets.AbsenceRequestTypeRepresentationViewSet,
93
+ basename="absencerequesttyperepresentation",
94
+ )
95
+
96
+
97
+ router.register(r"absenceplanner", viewsets.AbsenceRequestPlanner, basename="absenceplanner")
98
+ router.register(r"absencerequest", viewsets.AbsenceRequestModelViewSet)
99
+ router.register(r"absencerequesttype", viewsets.AbsenceRequestTypeModelViewSet, basename="absencerequesttype")
100
+
101
+ absence_request_router = WBCoreRouter()
102
+ absence_request_router.register(
103
+ r"periods",
104
+ viewsets.AbsenceRequestPeriodsAbsenceRequestModelViewSet,
105
+ basename="request-periods",
106
+ )
107
+
108
+ # Review Routers
109
+ router.register(r"reviewrepresentation", viewsets.ReviewRepresentationViewSet, basename="reviewrepresentation")
110
+ router.register(
111
+ r"reviewgrouprepresentation", viewsets.ReviewGroupRepresentationViewSet, basename="reviewgrouprepresentation"
112
+ )
113
+ router.register(
114
+ r"reviewquestioncategoryrepresentation",
115
+ viewsets.ReviewQuestionCategoryRepresentationViewSet,
116
+ basename="reviewquestioncategoryrepresentation",
117
+ )
118
+ router.register(
119
+ r"reviewquestionrepresentation",
120
+ viewsets.ReviewQuestionRepresentationViewSet,
121
+ basename="reviewquestionrepresentation",
122
+ )
123
+
124
+ router.register(r"reviewgroup", viewsets.ReviewGroupModelViewSet, basename="reviewgroup")
125
+
126
+ router.register(r"review", viewsets.ReviewModelViewSet, basename="review")
127
+ router.register(r"reviewtemplate", viewsets.ReviewTemplateModelViewSet, basename="reviewtemplate")
128
+
129
+ router.register(
130
+ r"reviewquestioncategory", viewsets.ReviewQuestionCategoryModelViewSet, basename="reviewquestioncategory"
131
+ )
132
+
133
+ router.register(r"reviewquestion", viewsets.ReviewQuestionModelViewSet, basename="reviewquestion")
134
+ router.register(r"reviewanswer", viewsets.ReviewAnswerModelViewSet, basename="reviewanswer")
135
+ router.register(r"absencetable", viewsets.AbsenceTablePandasViewSet, basename="absencetable")
136
+
137
+ reviewgroup_router = WBCoreRouter()
138
+ reviewgroup_router.register(r"review", viewsets.ReviewReviewGroupModelViewSet, basename="reviewgroup-review")
139
+
140
+ review_router = WBCoreRouter()
141
+ review_router.register(r"reviewquestion", viewsets.ReviewQuestionReviewModelViewSet, basename="review-reviewquestion")
142
+ review_router.register(
143
+ r"reviewquestionnocategory",
144
+ viewsets.ReviewQuestionReviewNoCategoryModelViewSet,
145
+ basename="review-reviewquestionnocategory",
146
+ )
147
+ review_router.register(
148
+ r"reviewanswerquestionnocategory",
149
+ viewsets.ReviewAnswerReviewNoCategoryModelViewSet,
150
+ basename="review-reviewanswerquestionnocategory",
151
+ )
152
+
153
+
154
+ review_category_router = WBCoreRouter()
155
+ review_category_router.register(
156
+ r"reviewquestioncategory",
157
+ viewsets.ReviewQuestionReviewCategoryModelViewSet,
158
+ basename="review-reviewquestioncategory",
159
+ )
160
+ review_category_router.register(
161
+ r"reviewanswerquestioncategory",
162
+ viewsets.ReviewAnswerReviewQuestionCategoryModelViewSet,
163
+ basename="review-reviewanswerquestioncategory",
164
+ )
165
+
166
+ review_question_category_router = WBCoreRouter()
167
+ review_question_category_router.register(
168
+ r"reviewquestion",
169
+ viewsets.ReviewQuestionReviewQuestionCategoryModelViewSet,
170
+ basename="reviewquestioncategory-reviewquestion",
171
+ )
172
+
173
+ # KPI routers
174
+ router.register(r"kpi", viewsets.KPIModelViewSet, basename="kpi")
175
+ router.register(r"kpirepresentation", viewsets.KPIRepresentationViewSet, basename="kpirepresentation")
176
+ router.register(
177
+ r"evaluationrepresentation", viewsets.EvaluationRepresentationViewSet, basename="evaluationrepresentation"
178
+ )
179
+
180
+ kpi_router = WBCoreRouter()
181
+
182
+ kpi_router.register(
183
+ r"evaluation",
184
+ viewsets.KPIEvaluationModelViewSet,
185
+ basename="kpi-evaluation",
186
+ )
187
+ kpi_router.register(r"evaluationgraph", viewsets.EvaluationGraphChartViewset, basename="kpi-evaluationgraph")
188
+
189
+
190
+ # Review pandas views
191
+ router.register("reviewprogress", viewsets.ReviewProgressPandasViewSet, basename="reviewprogress")
192
+ router.register("kpievaluationpandas", viewsets.KPIEvaluationPandasViewSet, basename="kpievaluationpandas")
193
+ review_router.register(r"progress", viewsets.ReviewProgressReviewPandasViewSet, basename="review-progress")
194
+ review_router.register(
195
+ r"reviewanswerpandasview", viewsets.ReviewAnswerReviewPandasViewSet, basename="review-reviewanswerpandasview"
196
+ )
197
+
198
+ urlpatterns = [
199
+ path("", include(router.urls)),
200
+ path(
201
+ "absencerequesttype/<int:absencerequesttype_id>/",
202
+ include(absencerequesttype_router.urls),
203
+ ),
204
+ path(
205
+ "employee/<int:employee_id>/",
206
+ include(employee_router.urls),
207
+ ),
208
+ path(
209
+ "request/<int:request_id>/",
210
+ include(absence_request_router.urls),
211
+ ),
212
+ path(
213
+ "calendar/<int:calendar_id>/",
214
+ include(calendar_router.urls),
215
+ ),
216
+ path("reviewgroup/<int:review_group_id>/", include(reviewgroup_router.urls)),
217
+ path("review/<int:review_id>/", include(review_router.urls)),
218
+ path("review/<int:review_id>/category/<int:category_id>/", include(review_category_router.urls)),
219
+ path("reviewquestioncategory/<int:category_id>/", include(review_question_category_router.urls)),
220
+ path("kpi/<int:kpi_id>/", include(kpi_router.urls)),
221
+ ]
@@ -0,0 +1,43 @@
1
+ from collections import OrderedDict, defaultdict
2
+ from datetime import datetime
3
+
4
+ from dateutil import rrule
5
+ from django.utils import timezone
6
+
7
+
8
+ def get_number_of_hours_between_dates(
9
+ d1, d2, list_employee_dayoffs, list_public_holidays=False, hours_range=None, granularity=12
10
+ ):
11
+ if hours_range is None:
12
+ hours_range = range(0, 23)
13
+
14
+ def convert_days_from_hours(hours, granularity, hours_per_day):
15
+ return int(hours / granularity) * granularity / hours_per_day
16
+
17
+ rules = rrule.rruleset()
18
+
19
+ byweekday_list = [rrule.MO, rrule.TU, rrule.WE, rrule.TH, rrule.FR, rrule.SA, rrule.SU]
20
+
21
+ rules.rrule(
22
+ rrule.rrule(
23
+ freq=rrule.HOURLY,
24
+ byweekday=byweekday_list,
25
+ byhour=hours_range,
26
+ dtstart=d1,
27
+ until=d2,
28
+ )
29
+ )
30
+ if list_public_holidays:
31
+ list_employee_dayoffs.extend(list_public_holidays)
32
+ current_tz = timezone.get_current_timezone()
33
+ for holiday in sorted(list_employee_dayoffs):
34
+ s1 = datetime(holiday.year, holiday.month, holiday.day, 0, 0, 0, tzinfo=current_tz)
35
+ s2 = datetime(holiday.year, holiday.month, holiday.day, 23, 59, 59, tzinfo=current_tz)
36
+ rules.exrule(rrule.rrule(rrule.HOURLY, dtstart=s1, until=s2))
37
+ dates = defaultdict(int)
38
+ for r in list(rules):
39
+ dates[r.date()] += 1
40
+ final = OrderedDict()
41
+ for k, v in dates.items():
42
+ final[k] = convert_days_from_hours(v, granularity, len(hours_range))
43
+ return final
@@ -0,0 +1,61 @@
1
+ from .absence import (
2
+ AbsenceRequestCrossBorderCountryModelViewSet,
3
+ AbsenceRequestEmployeeHumanResourceModelViewset,
4
+ AbsenceRequestModelViewSet,
5
+ AbsenceRequestPeriodsAbsenceRequestModelViewSet,
6
+ AbsenceRequestTypeModelViewSet,
7
+ AbsenceRequestTypeRepresentationViewSet,
8
+ AbsenceTypeCountEmployeeModelViewSet,
9
+ )
10
+ from .absence_charts import AbsenceRequestPlanner, AbsenceTablePandasViewSet
11
+ from .calendars import (
12
+ DayOffCalendarModelViewSet,
13
+ DayOffCalendarRepresentationViewSet,
14
+ DayOffDayOffCalendarModelViewSet,
15
+ DayOffModelViewSet,
16
+ DayOffRepresentationViewSet,
17
+ DefaultDailyPeriodDayOffCalendarModelViewSet,
18
+ DefaultDailyPeriodRepresentationViewSet,
19
+ EmployeeWeeklyOffPeriodsRepresentationViewSet,
20
+ )
21
+ from .employee import (
22
+ EmployeeBalanceModelViewSet,
23
+ EmployeeHumanResourceRepresentationViewSet,
24
+ EmployeeModelViewSet,
25
+ EmployeeYearBalanceRepresentationViewSet,
26
+ PositionModelViewSet,
27
+ PositionRepresentationViewSet,
28
+ WeeklyOffPeriodEmployeeHumanResourceModelViewSet,
29
+ YearBalanceEmployeeHumanResourceModelViewset,
30
+ )
31
+ from .kpi import (
32
+ EvaluationGraphChartViewset,
33
+ EvaluationRepresentationViewSet,
34
+ KPIEvaluationModelViewSet,
35
+ KPIEvaluationPandasViewSet,
36
+ KPIModelViewSet,
37
+ KPIRepresentationViewSet,
38
+ )
39
+ from .review import (
40
+ ReviewAnswerReviewPandasViewSet,
41
+ ReviewAnswerModelViewSet,
42
+ ReviewAnswerRepresentationViewSet,
43
+ ReviewAnswerReviewNoCategoryModelViewSet,
44
+ ReviewAnswerReviewQuestionCategoryModelViewSet,
45
+ ReviewGroupModelViewSet,
46
+ ReviewGroupRepresentationViewSet,
47
+ ReviewModelViewSet,
48
+ ReviewProgressPandasViewSet,
49
+ ReviewProgressReviewPandasViewSet,
50
+ ReviewQuestionCategoryModelViewSet,
51
+ ReviewQuestionCategoryRepresentationViewSet,
52
+ ReviewQuestionModelViewSet,
53
+ ReviewQuestionRepresentationViewSet,
54
+ ReviewQuestionReviewCategoryModelViewSet,
55
+ ReviewQuestionReviewModelViewSet,
56
+ ReviewQuestionReviewNoCategoryModelViewSet,
57
+ ReviewQuestionReviewQuestionCategoryModelViewSet,
58
+ ReviewRepresentationViewSet,
59
+ ReviewReviewGroupModelViewSet,
60
+ ReviewTemplateModelViewSet,
61
+ )