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.
- wbhuman_resources/__init__.py +1 -0
- wbhuman_resources/admin/__init__.py +5 -0
- wbhuman_resources/admin/absence.py +113 -0
- wbhuman_resources/admin/calendars.py +37 -0
- wbhuman_resources/admin/employee.py +109 -0
- wbhuman_resources/admin/kpi.py +21 -0
- wbhuman_resources/admin/review.py +157 -0
- wbhuman_resources/apps.py +23 -0
- wbhuman_resources/dynamic_preferences_registry.py +119 -0
- wbhuman_resources/factories/__init__.py +38 -0
- wbhuman_resources/factories/absence.py +109 -0
- wbhuman_resources/factories/calendars.py +60 -0
- wbhuman_resources/factories/employee.py +80 -0
- wbhuman_resources/factories/kpi.py +155 -0
- wbhuman_resources/filters/__init__.py +20 -0
- wbhuman_resources/filters/absence.py +109 -0
- wbhuman_resources/filters/absence_graphs.py +85 -0
- wbhuman_resources/filters/calendars.py +28 -0
- wbhuman_resources/filters/employee.py +81 -0
- wbhuman_resources/filters/kpi.py +35 -0
- wbhuman_resources/filters/review.py +134 -0
- wbhuman_resources/filters/signals.py +27 -0
- wbhuman_resources/locale/de/LC_MESSAGES/django.mo +0 -0
- wbhuman_resources/locale/de/LC_MESSAGES/django.po +2207 -0
- wbhuman_resources/locale/de/LC_MESSAGES/django.po.translated +2456 -0
- wbhuman_resources/locale/en/LC_MESSAGES/django.mo +0 -0
- wbhuman_resources/locale/en/LC_MESSAGES/django.po +2091 -0
- wbhuman_resources/locale/fr/LC_MESSAGES/django.mo +0 -0
- wbhuman_resources/locale/fr/LC_MESSAGES/django.po +2093 -0
- wbhuman_resources/management/__init__.py +23 -0
- wbhuman_resources/migrations/0001_initial_squashed_squashed_0015_alter_absencerequest_calendaritem_ptr_and_more.py +949 -0
- wbhuman_resources/migrations/0016_alter_employeehumanresource_options.py +20 -0
- wbhuman_resources/migrations/0017_absencerequest_crossborder_country_and_more.py +55 -0
- wbhuman_resources/migrations/0018_remove_position_group_position_groups.py +32 -0
- wbhuman_resources/migrations/0019_alter_absencerequest_options_alter_kpi_options_and_more.py +44 -0
- wbhuman_resources/migrations/0020_alter_employeeyearbalance_year_alter_review_year.py +27 -0
- wbhuman_resources/migrations/0021_alter_position_color.py +18 -0
- wbhuman_resources/migrations/0022_remove_review_editable_mode.py +64 -0
- wbhuman_resources/migrations/__init__.py +0 -0
- wbhuman_resources/models/__init__.py +23 -0
- wbhuman_resources/models/absence.py +903 -0
- wbhuman_resources/models/calendars.py +370 -0
- wbhuman_resources/models/employee.py +1241 -0
- wbhuman_resources/models/kpi.py +199 -0
- wbhuman_resources/models/preferences.py +40 -0
- wbhuman_resources/models/review.py +982 -0
- wbhuman_resources/permissions/__init__.py +0 -0
- wbhuman_resources/permissions/backend.py +26 -0
- wbhuman_resources/serializers/__init__.py +49 -0
- wbhuman_resources/serializers/absence.py +308 -0
- wbhuman_resources/serializers/calendars.py +73 -0
- wbhuman_resources/serializers/employee.py +267 -0
- wbhuman_resources/serializers/kpi.py +80 -0
- wbhuman_resources/serializers/review.py +415 -0
- wbhuman_resources/signals.py +4 -0
- wbhuman_resources/tasks.py +195 -0
- wbhuman_resources/templates/review/review_report.html +322 -0
- wbhuman_resources/tests/__init__.py +1 -0
- wbhuman_resources/tests/conftest.py +96 -0
- wbhuman_resources/tests/models/__init__.py +0 -0
- wbhuman_resources/tests/models/test_absences.py +478 -0
- wbhuman_resources/tests/models/test_calendars.py +209 -0
- wbhuman_resources/tests/models/test_employees.py +502 -0
- wbhuman_resources/tests/models/test_review.py +103 -0
- wbhuman_resources/tests/models/test_utils.py +110 -0
- wbhuman_resources/tests/signals.py +108 -0
- wbhuman_resources/tests/test_permission.py +64 -0
- wbhuman_resources/tests/test_tasks.py +74 -0
- wbhuman_resources/urls.py +221 -0
- wbhuman_resources/utils.py +43 -0
- wbhuman_resources/viewsets/__init__.py +61 -0
- wbhuman_resources/viewsets/absence.py +312 -0
- wbhuman_resources/viewsets/absence_charts.py +328 -0
- wbhuman_resources/viewsets/buttons/__init__.py +7 -0
- wbhuman_resources/viewsets/buttons/absence.py +32 -0
- wbhuman_resources/viewsets/buttons/employee.py +44 -0
- wbhuman_resources/viewsets/buttons/kpis.py +16 -0
- wbhuman_resources/viewsets/buttons/review.py +195 -0
- wbhuman_resources/viewsets/calendars.py +103 -0
- wbhuman_resources/viewsets/display/__init__.py +39 -0
- wbhuman_resources/viewsets/display/absence.py +334 -0
- wbhuman_resources/viewsets/display/calendars.py +83 -0
- wbhuman_resources/viewsets/display/employee.py +254 -0
- wbhuman_resources/viewsets/display/kpis.py +92 -0
- wbhuman_resources/viewsets/display/review.py +429 -0
- wbhuman_resources/viewsets/employee.py +210 -0
- wbhuman_resources/viewsets/endpoints/__init__.py +42 -0
- wbhuman_resources/viewsets/endpoints/absence.py +57 -0
- wbhuman_resources/viewsets/endpoints/calendars.py +18 -0
- wbhuman_resources/viewsets/endpoints/employee.py +51 -0
- wbhuman_resources/viewsets/endpoints/kpis.py +53 -0
- wbhuman_resources/viewsets/endpoints/review.py +191 -0
- wbhuman_resources/viewsets/kpi.py +280 -0
- wbhuman_resources/viewsets/menu/__init__.py +22 -0
- wbhuman_resources/viewsets/menu/absence.py +50 -0
- wbhuman_resources/viewsets/menu/administration.py +15 -0
- wbhuman_resources/viewsets/menu/calendars.py +33 -0
- wbhuman_resources/viewsets/menu/employee.py +44 -0
- wbhuman_resources/viewsets/menu/kpis.py +18 -0
- wbhuman_resources/viewsets/menu/review.py +97 -0
- wbhuman_resources/viewsets/mixins.py +14 -0
- wbhuman_resources/viewsets/review.py +837 -0
- wbhuman_resources/viewsets/titles/__init__.py +18 -0
- wbhuman_resources/viewsets/titles/absence.py +30 -0
- wbhuman_resources/viewsets/titles/employee.py +18 -0
- wbhuman_resources/viewsets/titles/kpis.py +15 -0
- wbhuman_resources/viewsets/titles/review.py +62 -0
- wbhuman_resources/viewsets/utils.py +28 -0
- wbhuman_resources-1.58.4.dist-info/METADATA +8 -0
- wbhuman_resources-1.58.4.dist-info/RECORD +111 -0
- 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
|