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,502 @@
1
+ import zoneinfo
2
+ from datetime import date, datetime, time, timedelta
3
+
4
+ import pandas as pd
5
+ import pytest
6
+ from django.contrib.auth.models import Group, Permission
7
+ from django.contrib.contenttypes.models import ContentType
8
+ from django.db import models
9
+ from dynamic_preferences.registries import global_preferences_registry
10
+ from faker import Faker
11
+ from psycopg.types.range import TimestamptzRange
12
+ from wbcore.contrib.directory.models import EmployerEmployeeRelationship
13
+
14
+ from wbhuman_resources.factories import (
15
+ DayOffCalendarFactory,
16
+ DayOffFactory,
17
+ EmployeeYearBalanceFactory,
18
+ TimeOffRequestFactory,
19
+ VacationRequestFactory,
20
+ )
21
+ from wbhuman_resources.models.employee import (
22
+ EmployeeHumanResource,
23
+ EmployeeWeeklyOffPeriods,
24
+ EmployeeYearBalance,
25
+ post_save_employee,
26
+ )
27
+ from wbhuman_resources.models.preferences import (
28
+ get_previous_year_balance_expiration_date,
29
+ )
30
+
31
+ this_year_minimum_date_to_consume_previous_year_balance = get_previous_year_balance_expiration_date(date.today().year)
32
+
33
+
34
+ fake = Faker()
35
+
36
+
37
+ @pytest.mark.django_db
38
+ class TestEmployeeHumanResource:
39
+ # Permission base tests
40
+ def test_get_administrators(self, employee_human_resource, user_factory):
41
+ user_factory.create(is_superuser=True) # superuser
42
+ admin = user_factory.create()
43
+ admin.user_permissions.add(Permission.objects.get(codename="administrate_absencerequest"))
44
+ user_factory.create() # normal_user
45
+ assert set(EmployeeHumanResource.get_administrators()) == {admin}
46
+
47
+ def test_is_administrator_super_user(self, user_factory):
48
+ superuser = user_factory.create(is_superuser=True)
49
+ assert EmployeeHumanResource.is_administrator(superuser)
50
+
51
+ def test_is_administrator_normal_user(self, user):
52
+ assert not EmployeeHumanResource.is_administrator(user)
53
+
54
+ def test_is_administrator_user_with_permission(self, user_factory):
55
+ admin = user_factory.create()
56
+ admin.user_permissions.add(Permission.objects.get(codename="administrate_absencerequest"))
57
+ assert EmployeeHumanResource.is_administrator(admin)
58
+
59
+ def test_get_managed_employees(self, employee_human_resource_factory, position_factory):
60
+ top_manager = employee_human_resource_factory.create()
61
+ manager1 = employee_human_resource_factory.create()
62
+ manager2 = employee_human_resource_factory.create()
63
+
64
+ top_position = position_factory.create(manager=top_manager.profile)
65
+ pos_1 = position_factory.create(parent=top_position, manager=manager1.profile)
66
+ pos_2 = position_factory.create(parent=top_position, manager=manager2.profile)
67
+
68
+ top_manager.position = top_position
69
+ top_manager.save()
70
+ manager1.position = pos_1
71
+ manager1.save()
72
+ manager2.position = pos_2
73
+ manager2.save()
74
+
75
+ top_employee = employee_human_resource_factory.create(position=top_position)
76
+ employee_1 = employee_human_resource_factory.create(position=pos_1)
77
+ employee_2 = employee_human_resource_factory.create(position=pos_2)
78
+
79
+ employee_without_pos = employee_human_resource_factory.create(direct_manager=manager1.profile)
80
+ top_manager_employees = {top_manager, manager1, manager2, top_employee, employee_1, employee_2}
81
+ assert set(top_manager.get_managed_employees(include_self=False)) == {
82
+ manager1,
83
+ manager2,
84
+ top_employee,
85
+ employee_1,
86
+ employee_2,
87
+ }
88
+ assert set(top_manager.get_managed_employees(include_self=True)) == top_manager_employees
89
+ for managee in top_manager_employees:
90
+ assert top_manager.is_manager_of(managee, include_self=True)
91
+
92
+ manager1_employees = {employee_1, manager1, employee_without_pos}
93
+ assert set(manager1.get_managed_employees(include_self=True)) == manager1_employees
94
+ assert set(manager1.get_managed_employees(include_self=False)) == {employee_1, employee_without_pos}
95
+ for managee in manager1_employees:
96
+ assert manager1.is_manager_of(managee, include_self=True)
97
+
98
+ manager2_employees = {employee_2, manager2}
99
+ assert set(manager2.get_managed_employees(include_self=True)) == manager2_employees
100
+ assert set(manager2.get_managed_employees(include_self=False)) == {employee_2}
101
+ for managee in manager2_employees:
102
+ assert manager2.is_manager_of(managee, include_self=True)
103
+
104
+ assert not employee_1.get_managed_employees(include_self=False).exists()
105
+ assert not employee_2.get_managed_employees(include_self=False).exists()
106
+ assert not employee_without_pos.get_managed_employees(include_self=False).exists()
107
+
108
+ def test_get_managers(self, position_factory, employee_human_resource_factory):
109
+ position_manager = employee_human_resource_factory.create()
110
+ main_manager = employee_human_resource_factory.create()
111
+
112
+ position = position_factory.create(manager=position_manager.profile)
113
+ assert (
114
+ next(
115
+ employee_human_resource_factory.create(
116
+ direct_manager=main_manager.profile, position=position
117
+ ).get_managers()
118
+ )
119
+ == main_manager.profile
120
+ )
121
+ assert (
122
+ next(employee_human_resource_factory.create(direct_manager=None, position=position).get_managers())
123
+ == position_manager.profile
124
+ )
125
+ with pytest.raises(StopIteration):
126
+ next(employee_human_resource_factory.create(direct_manager=None, position=None).get_managers())
127
+
128
+ def test_get_managers_with_global_manager(
129
+ self, authenticated_person_factory, position_factory, employee_human_resource_factory
130
+ ):
131
+ global_manager_permission = Permission.objects.get(
132
+ codename="administrate_employeehumanresource",
133
+ content_type=ContentType.objects.get_for_model(EmployeeHumanResource),
134
+ )
135
+ administrate_employeehumanresource = authenticated_person_factory.create()
136
+ administrate_employeehumanresource.user_account.user_permissions.add(global_manager_permission)
137
+ assert (
138
+ next(employee_human_resource_factory.create(direct_manager=None, position=None).get_managers())
139
+ == administrate_employeehumanresource
140
+ )
141
+
142
+ @pytest.mark.parametrize("val_date", [(fake.date_this_decade())])
143
+ def test_deactivate(self, employee_human_resource_factory, val_date):
144
+ employee = employee_human_resource_factory.create(enrollment_at=val_date)
145
+ employee.deactivate(disenrollment_date=date(val_date.year, 12, 31))
146
+ assert not employee.is_active
147
+ assert not employee.profile.user_account.is_active
148
+ balance = employee.balances.get(year=val_date.year)
149
+ assert balance.monthly_allowances.count() == 1
150
+
151
+ def test_assign_unassign_position_groups(self, employee_human_resource):
152
+ assert not employee_human_resource.profile.user_account.groups.exists()
153
+ employee_human_resource.assign_position_groups()
154
+ assert employee_human_resource.profile.user_account.groups.filter(
155
+ id=employee_human_resource.position.groups.first().id
156
+ ).exists()
157
+ employee_human_resource.unassign_position_groups()
158
+ assert not employee_human_resource.profile.user_account.groups.exists()
159
+
160
+ # Balance & Usage tests
161
+ def test_post_save_employee(self, employee_human_resource, company):
162
+ # Set main CRM company
163
+ global_preferences_registry.manager()["directory__main_company"] = company.id
164
+ global_preferences_registry.manager().cache.clear()
165
+
166
+ post_save_employee("SENDER ", employee_human_resource, created=True)
167
+
168
+ # Expect the weekend days to be labeled as off period by default
169
+ assert EmployeeWeeklyOffPeriods.objects.filter(employee=employee_human_resource, weekday=5).count() == 2
170
+ assert EmployeeWeeklyOffPeriods.objects.filter(employee=employee_human_resource, weekday=6).count() == 2
171
+
172
+ # Expect the employee to have balance and monthly allowance for at its enrolment year
173
+ balance = employee_human_resource.balances.get(year=employee_human_resource.enrollment_at.year)
174
+ assert balance.monthly_allowances.count() == 1
175
+
176
+ # Expect the CRM position to reflect the
177
+ assert EmployerEmployeeRelationship.objects.filter(
178
+ employee=employee_human_resource.profile,
179
+ employer=company,
180
+ primary=True,
181
+ position__title=employee_human_resource.position.name,
182
+ ).exists()
183
+ assert employee_human_resource.profile.user_account.groups.filter(
184
+ id=employee_human_resource.position.groups.first().id
185
+ ).exists()
186
+
187
+ @pytest.mark.parametrize("start_date", [(fake.date_object())])
188
+ def test_extract_workable_periods_basic_usage(
189
+ self, employee_human_resource, start_date, day_off_factory, employee_weekly_off_periods_factory
190
+ ):
191
+ end_date = start_date + timedelta(days=7)
192
+ start_datetime = datetime.combine(start_date, time(0, 0, 0)).astimezone(zoneinfo.ZoneInfo("UTC"))
193
+ end_datetime = datetime.combine(end_date, time(23, 59, 59)).astimezone(zoneinfo.ZoneInfo("UTC"))
194
+ day_off = day_off_factory.create(
195
+ calendar=employee_human_resource.calendar, date=fake.date_between(start_date, end_date)
196
+ )
197
+ period_off = employee_weekly_off_periods_factory.create(employee=employee_human_resource)
198
+ res = set(employee_human_resource.extract_workable_periods(start_datetime, end_datetime))
199
+ expected_res = set()
200
+ for i in range(8):
201
+ _d = start_date + timedelta(days=i)
202
+ if day_off.date != _d:
203
+ for period in employee_human_resource.calendar.default_periods.all():
204
+ if period_off.period != period or period_off.weekday != _d.weekday():
205
+ expected_res.add((_d, period))
206
+ assert expected_res == res
207
+
208
+ def test_assign_vacation_allowance_from_range(self, employee_human_resource):
209
+ pass
210
+
211
+ @pytest.mark.parametrize("year_str", [(fake.year())])
212
+ def test_get_or_create_balance(self, employee_human_resource, year_str):
213
+ assert employee_human_resource.get_or_create_balance(int(year_str))[0].year == int(year_str)
214
+
215
+
216
+ @pytest.mark.django_db
217
+ class TestEmployeeYearBalance:
218
+ @pytest.fixture
219
+ def test_args(self, start_date):
220
+ balance = EmployeeYearBalanceFactory.create(year=start_date.year)
221
+ default_period1 = balance.employee.calendar.default_periods.earliest("lower_time")
222
+ default_period2 = balance.employee.calendar.default_periods.latest("lower_time")
223
+
224
+ start_r2 = fake.date_between_dates(start_date, date(balance.year, 12, 31))
225
+ start_r3 = fake.date_between_dates(date(balance.year, 1, 1), start_date)
226
+ vacation_request1 = VacationRequestFactory.create(
227
+ employee=balance.employee,
228
+ period=TimestamptzRange(
229
+ default_period1.get_lower_datetime(start_date),
230
+ default_period2.get_upper_datetime(start_date) + timedelta(days=2),
231
+ ),
232
+ )
233
+ vacation_request2 = VacationRequestFactory.create(
234
+ employee=balance.employee,
235
+ period=TimestamptzRange(
236
+ default_period1.get_lower_datetime(start_r2),
237
+ default_period2.get_upper_datetime(start_r2) + timedelta(days=2),
238
+ ),
239
+ )
240
+ timeoff_request = TimeOffRequestFactory.create(
241
+ employee=balance.employee,
242
+ period=TimestamptzRange(
243
+ default_period1.get_lower_datetime(start_r3),
244
+ default_period2.get_upper_datetime(start_r3) + timedelta(days=2),
245
+ ),
246
+ )
247
+
248
+ mandatatory_day_off = DayOffFactory.create(
249
+ calendar=balance.employee.calendar,
250
+ count_as_holiday=False,
251
+ date=fake.date_between_dates(date(balance.year, 1, 5), date(balance.year, 12, 31)),
252
+ )
253
+ DayOffFactory.create(
254
+ calendar=DayOffCalendarFactory.create(title="other calendar", resource="other.resource"),
255
+ count_as_holiday=False,
256
+ date=fake.date_between_dates(date(balance.year, 1, 5), date(balance.year, 12, 31)),
257
+ ) # Other calendar
258
+ return (
259
+ balance,
260
+ default_period1,
261
+ default_period2,
262
+ vacation_request1,
263
+ vacation_request2,
264
+ timeoff_request,
265
+ mandatatory_day_off,
266
+ )
267
+
268
+ # test properties
269
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
270
+ def test_balance(self, test_args, start_date):
271
+ [
272
+ balance,
273
+ default_period1,
274
+ default_period2,
275
+ vacation_request1,
276
+ vacation_request2,
277
+ timeoff_request,
278
+ mandatatory_day_off,
279
+ ] = test_args
280
+ expected_res = (
281
+ balance.extra_balance + balance.monthly_allowances.aggregate(s=models.Sum("hourly_allowance"))["s"]
282
+ )
283
+ assert balance.balance == expected_res
284
+ assert EmployeeYearBalance.objects.get(id=balance.id)._balance == expected_res
285
+
286
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
287
+ def test_daily_hours(self, test_args, start_date):
288
+ [
289
+ balance,
290
+ default_period1,
291
+ default_period2,
292
+ vacation_request1,
293
+ vacation_request2,
294
+ timeoff_request,
295
+ mandatatory_day_off,
296
+ ] = test_args
297
+
298
+ expected_res = default_period1.total_hours + default_period2.total_hours
299
+ assert balance.daily_hours == expected_res
300
+ assert EmployeeYearBalance.objects.get(id=balance.id)._daily_hours == expected_res
301
+
302
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
303
+ def test_number_mandatory_days_off_in_days(self, test_args, start_date):
304
+ [
305
+ balance,
306
+ default_period1,
307
+ default_period2,
308
+ vacation_request1,
309
+ vacation_request2,
310
+ timeoff_request,
311
+ mandatatory_day_off,
312
+ ] = test_args
313
+ expected_res = 1
314
+ assert balance.number_mandatory_days_off_in_days == expected_res
315
+ assert EmployeeYearBalance.objects.get(id=balance.id)._number_mandatory_days_off_in_days == expected_res
316
+
317
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
318
+ def test_number_mandatory_days_off(self, test_args, start_date):
319
+ [
320
+ balance,
321
+ default_period1,
322
+ default_period2,
323
+ vacation_request1,
324
+ vacation_request2,
325
+ timeoff_request,
326
+ mandatatory_day_off,
327
+ ] = test_args
328
+ expected_res = balance.employee.calendar.get_daily_hours()
329
+ assert balance.number_mandatory_days_off == expected_res
330
+ assert EmployeeYearBalance.objects.get(id=balance.id)._number_mandatory_days_off == expected_res
331
+
332
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
333
+ def test_total_vacation_hourly_usage(self, test_args, start_date):
334
+ [
335
+ balance,
336
+ default_period1,
337
+ default_period2,
338
+ vacation_request1,
339
+ vacation_request2,
340
+ timeoff_request,
341
+ mandatatory_day_off,
342
+ ] = test_args
343
+ expected_res = vacation_request1.total_hours + vacation_request2.total_hours
344
+ assert balance.total_vacation_hourly_usage == expected_res
345
+ assert EmployeeYearBalance.objects.get(id=balance.id)._total_vacation_hourly_usage == expected_res
346
+
347
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
348
+ def test_total_vacation_hourly_balance(self, test_args, start_date):
349
+ [
350
+ balance,
351
+ default_period1,
352
+ default_period2,
353
+ vacation_request1,
354
+ vacation_request2,
355
+ timeoff_request,
356
+ mandatatory_day_off,
357
+ ] = test_args
358
+
359
+ expected_res = (
360
+ balance.extra_balance
361
+ + balance.monthly_allowances.aggregate(s=models.Sum("hourly_allowance"))["s"]
362
+ - vacation_request1.total_hours
363
+ - vacation_request2.total_hours
364
+ - balance.employee.calendar.get_daily_hours()
365
+ )
366
+ assert balance.total_vacation_hourly_balance == expected_res
367
+ assert EmployeeYearBalance.objects.get(id=balance.id)._total_vacation_hourly_balance == expected_res
368
+
369
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
370
+ def test_balance_in_days(self, test_args, start_date):
371
+ [
372
+ balance,
373
+ default_period1,
374
+ default_period2,
375
+ vacation_request1,
376
+ vacation_request2,
377
+ timeoff_request,
378
+ mandatatory_day_off,
379
+ ] = test_args
380
+ expected_res = (
381
+ balance.extra_balance + balance.monthly_allowances.aggregate(s=models.Sum("hourly_allowance"))["s"]
382
+ ) / balance.employee.calendar.get_daily_hours()
383
+ assert balance.balance_in_days == expected_res
384
+ assert EmployeeYearBalance.objects.get(id=balance.id)._balance_in_days == expected_res
385
+
386
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
387
+ def test_total_vacation_hourly_usage_in_days(self, test_args, start_date):
388
+ [
389
+ balance,
390
+ default_period1,
391
+ default_period2,
392
+ vacation_request1,
393
+ vacation_request2,
394
+ timeoff_request,
395
+ mandatatory_day_off,
396
+ ] = test_args
397
+ expected_res = (
398
+ vacation_request1.total_hours + vacation_request2.total_hours
399
+ ) / balance.employee.calendar.get_daily_hours()
400
+ assert balance.total_vacation_hourly_usage_in_days == expected_res
401
+ assert EmployeeYearBalance.objects.get(id=balance.id)._total_vacation_hourly_usage_in_days == expected_res
402
+
403
+ @pytest.mark.parametrize("start_date", [(fake.date_this_year())])
404
+ def test_total_vacation_hourly_balance_in_days(self, test_args, start_date):
405
+ [
406
+ balance,
407
+ default_period1,
408
+ default_period2,
409
+ vacation_request1,
410
+ vacation_request2,
411
+ timeoff_request,
412
+ mandatatory_day_off,
413
+ ] = test_args
414
+ expected_res = (
415
+ balance.extra_balance
416
+ + balance.monthly_allowances.aggregate(s=models.Sum("hourly_allowance"))["s"]
417
+ - vacation_request1.total_hours
418
+ - vacation_request2.total_hours
419
+ - balance.employee.calendar.get_daily_hours()
420
+ ) / balance.employee.calendar.get_daily_hours()
421
+
422
+ assert balance.total_vacation_hourly_balance_in_days == expected_res
423
+ assert EmployeeYearBalance.objects.get(id=balance.id)._total_vacation_hourly_balance_in_days == expected_res
424
+
425
+
426
+ @pytest.mark.django_db
427
+ class TestEmployeeWeeklyOffPeriods:
428
+ @pytest.mark.parametrize("test_date", [(fake.date_this_year())])
429
+ def get_timespan(self, employee_weekly_off_periods, test_date):
430
+ timespan = employee_weekly_off_periods.get_timespan(test_date)
431
+ assert timespan.lower == employee_weekly_off_periods.period.get_lower_datetime(test_date)
432
+ assert timespan.upper == employee_weekly_off_periods.period.get_upper_datetime(test_date)
433
+
434
+ @pytest.mark.parametrize("test_date", [(fake.date_this_year())])
435
+ def test_get_employee_weekly_periods_df(
436
+ self, day_off_calendar, employee_human_resource_factory, employee_weekly_off_periods_factory, test_date
437
+ ):
438
+ test_date = (test_date - pd.tseries.offsets.Week(weekday=0)).date()
439
+ employee1 = employee_human_resource_factory.create(calendar=day_off_calendar, is_active=True)
440
+ employee2 = employee_human_resource_factory.create(calendar=day_off_calendar, is_active=True)
441
+ employee3 = employee_human_resource_factory.create(
442
+ calendar=day_off_calendar, is_active=False
443
+ ) # Expect this employee to not be present
444
+
445
+ period1 = employee_weekly_off_periods_factory.create(employee=employee1)
446
+ period2 = employee_weekly_off_periods_factory.create(employee=employee2)
447
+ period3 = employee_weekly_off_periods_factory.create(employee=employee3)
448
+ res = EmployeeWeeklyOffPeriods.get_employee_weekly_periods_df(
449
+ test_date, test_date + timedelta(days=7), employee__is_active=True
450
+ )
451
+ assert res.shape == (2, 5)
452
+ res = res.set_index(["employee", "period", "date"])
453
+ assert res.loc[
454
+ (employee1.id, period1.period.id, test_date + timedelta(days=period1.weekday)), :
455
+ ].values.tolist() == ["APPROVED", "Day Off"]
456
+ assert res.loc[
457
+ (employee2.id, period2.period.id, test_date + timedelta(days=period2.weekday)), :
458
+ ].values.tolist() == ["APPROVED", "Day Off"]
459
+ with pytest.raises(KeyError):
460
+ assert res.loc[(employee3.id, period3.period.id, test_date + timedelta(days=period3.weekday)), :]
461
+
462
+
463
+ @pytest.mark.django_db
464
+ class TestPosition:
465
+ def test_change_group_in_position_change_user_groups(self, employee_human_resource):
466
+ employee_human_resource.assign_position_groups()
467
+ position = employee_human_resource.position
468
+ first_group = position.groups.first()
469
+ second_group = Group.objects.create(name="second group")
470
+
471
+ assert set(employee_human_resource.profile.user_account.groups.all()) == {first_group}
472
+
473
+ position.groups.add(second_group)
474
+ assert set(employee_human_resource.profile.user_account.groups.all()) == {first_group, second_group}
475
+
476
+ position.groups.remove(second_group)
477
+ assert set(employee_human_resource.profile.user_account.groups.all()) == {first_group}
478
+
479
+ position.groups.remove(first_group)
480
+ assert set(employee_human_resource.profile.user_account.groups.all()) == set()
481
+
482
+ def test_get_employees(self, position_factory, employee_human_resource_factory):
483
+ root_1 = position_factory.create()
484
+ e_root_1 = employee_human_resource_factory.create(position=root_1)
485
+ child_1 = position_factory.create(parent=root_1)
486
+ e_child_1 = employee_human_resource_factory.create(position=child_1)
487
+ employee_human_resource_factory.create(
488
+ position=child_1, is_active=False
489
+ ) # Unactive employee, shouldn't show in the method
490
+
491
+ root_2 = position_factory.create()
492
+ e_root_2 = employee_human_resource_factory.create(position=root_2)
493
+ child_2 = position_factory.create(parent=root_2)
494
+ e_child_2 = employee_human_resource_factory.create(position=child_2)
495
+ employee_human_resource_factory.create(
496
+ position=child_2, is_active=False
497
+ ) # Unactive employee, shouldn't show in the method
498
+
499
+ assert {*root_1.get_employees().values_list("id", flat=True)} == {e_root_1.id, e_child_1.id}
500
+ assert {*child_1.get_employees().values_list("id", flat=True)} == {e_child_1.id}
501
+ assert {*root_2.get_employees().values_list("id", flat=True)} == {e_root_2.id, e_child_2.id}
502
+ assert {*child_2.get_employees().values_list("id", flat=True)} == {e_child_2.id}
@@ -0,0 +1,103 @@
1
+ import pytest
2
+ from django.forms.models import model_to_dict
3
+ from rest_framework.test import APIRequestFactory
4
+
5
+ from wbhuman_resources.factories import EmployeeHumanResourceFactory
6
+ from wbhuman_resources.factories.kpi import (
7
+ ReviewAnswerFactory,
8
+ ReviewFactory,
9
+ ReviewGroupFactory,
10
+ ReviewQuestionFactory,
11
+ )
12
+ from wbhuman_resources.models.review import Review, create_review_from_template
13
+
14
+
15
+ @pytest.mark.django_db
16
+ class TestEmployeeReview:
17
+ def test_get_reviewer(self):
18
+ employee = EmployeeHumanResourceFactory()
19
+ review = ReviewFactory(reviewee=employee.profile, reviewer=None)
20
+ review2 = ReviewFactory(reviewer=None)
21
+ assert review.reviewer
22
+ assert review2.reviewer is None
23
+
24
+ @pytest.mark.parametrize("with_data", [True, False])
25
+ def test_create_review_from_template(self, with_data):
26
+ if with_data:
27
+ employee = EmployeeHumanResourceFactory()
28
+ review_group = ReviewGroupFactory(employees=(employee.profile,))
29
+ obj = ReviewFactory(review_group=review_group)
30
+ data = {
31
+ "from_date": obj.from_date,
32
+ "to_date": obj.to_date,
33
+ "review_deadline": obj.review_deadline,
34
+ "auto_apply_deadline": obj.auto_apply_deadline,
35
+ "employees": list(obj.review_group.employees.values_list("id", flat=True)),
36
+ }
37
+ else:
38
+ data = {}
39
+
40
+ api_request = APIRequestFactory()
41
+ request = api_request.post("", data=data)
42
+ template = ReviewFactory(is_template=True)
43
+ from_date = request.POST.get("from_date", None)
44
+ to_date = request.POST.get("to_date", None)
45
+ review_deadline = request.POST.get("review_deadline", None)
46
+ auto_apply_deadline = request.POST.get("auto_apply_deadline", None)
47
+ employees = request.POST.get("employees", None)
48
+ include_kpi = request.POST.get("include_kpi", None)
49
+ nb_reviews = Review.objects.all().count()
50
+ create_review_from_template(
51
+ template.pk, from_date, to_date, review_deadline, auto_apply_deadline, employees, include_kpi
52
+ )
53
+ if with_data:
54
+ assert Review.objects.all().count() == nb_reviews + 1
55
+ else:
56
+ assert Review.objects.all().count() == nb_reviews
57
+
58
+ def test_clone_review(self):
59
+ template = ReviewFactory(is_template=True)
60
+ question = ReviewQuestionFactory(review=template)
61
+ cloned = template.clone()
62
+ dict_template = model_to_dict(template)
63
+ dict_cloned = model_to_dict(cloned)
64
+ assert cloned != template
65
+ assert dict_template.pop("id") != dict_cloned.pop("id")
66
+ assert dict_template == dict_cloned
67
+ assert template.questions.count() == cloned.questions.count() == 1
68
+ assert template.questions.first() != cloned.questions.first()
69
+
70
+ dict_question = model_to_dict(question)
71
+ dict_template_question = model_to_dict(template.questions.first())
72
+ dict_cloned_question = model_to_dict(cloned.questions.first())
73
+
74
+ assert question == template.questions.first() != cloned.questions.first()
75
+ assert dict_question.pop("id") == dict_template_question.pop("id") != dict_cloned_question.pop("id")
76
+ assert dict_question == dict_template_question != dict_cloned_question
77
+
78
+ @pytest.mark.parametrize("status", Review.Status.names)
79
+ def test_get_answer_categories_for_user(self, status, user_factory):
80
+ review = ReviewFactory(is_template=False, status=status)
81
+ user = user_factory()
82
+ question1 = ReviewQuestionFactory(review=review)
83
+ answer1 = ReviewAnswerFactory(question=question1)
84
+
85
+ question2 = ReviewQuestionFactory(review=review)
86
+ answer2 = ReviewAnswerFactory(question=question2, answered_by=user.profile)
87
+
88
+ categories = review.get_answer_categories_for_user(user=user)
89
+ if review.status in [Review.Status.FILL_IN_REVIEW, Review.Status.REVIEW]:
90
+ assert categories.count() == 1
91
+ assert categories.first() == answer2.question.category
92
+ else:
93
+ assert categories.count() == 2
94
+ assert set(categories) == {answer1.question.category, answer2.question.category}
95
+
96
+ def test_get_question_categories(self):
97
+ review = ReviewFactory(is_template=False)
98
+ question1 = ReviewQuestionFactory(review=review)
99
+
100
+ categories = review.get_question_categories()
101
+
102
+ assert categories.count() == 1
103
+ assert categories.first() == question1.category