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,478 @@
|
|
|
1
|
+
import zoneinfo
|
|
2
|
+
from datetime import date, timedelta
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import pytest
|
|
7
|
+
from django.contrib.auth.models import Group, Permission
|
|
8
|
+
from django.db.models import Sum
|
|
9
|
+
from faker import Faker
|
|
10
|
+
from psycopg.types.range import TimestamptzRange
|
|
11
|
+
|
|
12
|
+
from wbhuman_resources.models import (
|
|
13
|
+
AbsenceRequest,
|
|
14
|
+
AbsenceRequestPeriods,
|
|
15
|
+
AbsenceRequestType,
|
|
16
|
+
BalanceHourlyAllowance,
|
|
17
|
+
)
|
|
18
|
+
from wbhuman_resources.models.absence import (
|
|
19
|
+
can_cancel_request,
|
|
20
|
+
can_validate_or_deny_request,
|
|
21
|
+
)
|
|
22
|
+
from wbhuman_resources.models.preferences import (
|
|
23
|
+
get_previous_year_balance_expiration_date,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
fake = Faker()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.django_db
|
|
30
|
+
class TestAbsenceRequest:
|
|
31
|
+
@pytest.fixture()
|
|
32
|
+
def past_aware_datetime(self):
|
|
33
|
+
return fake.past_datetime().astimezone(zoneinfo.ZoneInfo("UTC"))
|
|
34
|
+
|
|
35
|
+
@pytest.fixture()
|
|
36
|
+
def future_aware_datetime(self):
|
|
37
|
+
return fake.future_datetime().astimezone(zoneinfo.ZoneInfo("UTC"))
|
|
38
|
+
|
|
39
|
+
def test_normal_user_cannot_cancel_past_request(self, vacation_request_factory, past_aware_datetime):
|
|
40
|
+
request = vacation_request_factory(
|
|
41
|
+
status=AbsenceRequest.Status.APPROVED,
|
|
42
|
+
period=TimestamptzRange(lower=past_aware_datetime - timedelta(days=2), upper=past_aware_datetime),
|
|
43
|
+
)
|
|
44
|
+
assert not can_cancel_request(request, request.employee.profile.user_account)
|
|
45
|
+
|
|
46
|
+
def test_normal_user_can_cancel_future_request(
|
|
47
|
+
self, vacation_request_factory, user_factory, future_aware_datetime, employee_human_resource_factory
|
|
48
|
+
):
|
|
49
|
+
admin = user_factory.create(is_active=True)
|
|
50
|
+
admin.user_permissions.add(Permission.objects.get(codename="administrate_absencerequest"))
|
|
51
|
+
manager = employee_human_resource_factory.create()
|
|
52
|
+
|
|
53
|
+
employee = employee_human_resource_factory.create(direct_manager=manager.profile)
|
|
54
|
+
request = vacation_request_factory(
|
|
55
|
+
employee=employee,
|
|
56
|
+
status=AbsenceRequest.Status.APPROVED,
|
|
57
|
+
period=TimestamptzRange(lower=future_aware_datetime, upper=future_aware_datetime + timedelta(days=2)),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
assert can_cancel_request(request, request.employee.profile.user_account)
|
|
61
|
+
assert can_cancel_request(request, admin)
|
|
62
|
+
assert can_cancel_request(request, manager.profile.user_account)
|
|
63
|
+
|
|
64
|
+
def test_can_validate_or_deny_request(
|
|
65
|
+
self, vacation_request_factory, user_factory, future_aware_datetime, employee_human_resource_factory
|
|
66
|
+
):
|
|
67
|
+
admin = user_factory.create(is_active=True)
|
|
68
|
+
admin.user_permissions.add(Permission.objects.get(codename="administrate_absencerequest"))
|
|
69
|
+
manager = employee_human_resource_factory.create()
|
|
70
|
+
|
|
71
|
+
employee = employee_human_resource_factory.create(direct_manager=manager.profile)
|
|
72
|
+
request = vacation_request_factory(
|
|
73
|
+
employee=employee,
|
|
74
|
+
status=AbsenceRequest.Status.APPROVED,
|
|
75
|
+
period=TimestamptzRange(lower=future_aware_datetime, upper=future_aware_datetime + timedelta(days=2)),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert not can_validate_or_deny_request(request, request.employee.profile.user_account)
|
|
79
|
+
assert can_validate_or_deny_request(request, admin)
|
|
80
|
+
assert can_validate_or_deny_request(request, manager.profile.user_account)
|
|
81
|
+
|
|
82
|
+
def test_submit(self, absence_request_factory, employee_human_resource):
|
|
83
|
+
request = absence_request_factory.create(status=AbsenceRequest.Status.DRAFT)
|
|
84
|
+
request.employee.direct_manager = employee_human_resource.profile
|
|
85
|
+
request.employee.save()
|
|
86
|
+
|
|
87
|
+
request.submit()
|
|
88
|
+
request.save()
|
|
89
|
+
assert request.status == AbsenceRequest.Status.PENDING
|
|
90
|
+
|
|
91
|
+
def test_approve(self, absence_request_factory):
|
|
92
|
+
request = absence_request_factory.create(status=AbsenceRequest.Status.PENDING)
|
|
93
|
+
|
|
94
|
+
request.approve()
|
|
95
|
+
request.save()
|
|
96
|
+
assert request.status == AbsenceRequest.Status.APPROVED
|
|
97
|
+
|
|
98
|
+
def test_deny_not_vacation_request(self, time_off_request_factory):
|
|
99
|
+
request = time_off_request_factory.create(status=AbsenceRequest.Status.PENDING)
|
|
100
|
+
request.deny()
|
|
101
|
+
request.save()
|
|
102
|
+
assert request.status == AbsenceRequest.Status.DENIED
|
|
103
|
+
|
|
104
|
+
def test_deny_vacation_request(self, vacation_request_factory):
|
|
105
|
+
request = vacation_request_factory.create(status=AbsenceRequest.Status.PENDING)
|
|
106
|
+
request.deny()
|
|
107
|
+
request.save()
|
|
108
|
+
assert request.status == AbsenceRequest.Status.DENIED
|
|
109
|
+
|
|
110
|
+
def test_backtodraft(self, absence_request_factory):
|
|
111
|
+
request = absence_request_factory.create(status=AbsenceRequest.Status.PENDING)
|
|
112
|
+
request.backtodraft()
|
|
113
|
+
request.save()
|
|
114
|
+
assert request.status == AbsenceRequest.Status.DRAFT
|
|
115
|
+
|
|
116
|
+
def test_cancel(self, absence_request_factory, employee_human_resource):
|
|
117
|
+
request = absence_request_factory.create(status=AbsenceRequest.Status.APPROVED)
|
|
118
|
+
request.employee.direct_manager = employee_human_resource.profile
|
|
119
|
+
request.employee.save()
|
|
120
|
+
|
|
121
|
+
request.cancel()
|
|
122
|
+
request.save()
|
|
123
|
+
assert request.status == AbsenceRequest.Status.CANCELLED
|
|
124
|
+
|
|
125
|
+
def test_timespan(self, absence_request_periods):
|
|
126
|
+
request = absence_request_periods.request
|
|
127
|
+
assert (
|
|
128
|
+
request.periods_timespan.lower
|
|
129
|
+
== request.periods.earliest("timespan__startswith").request.periods_timespan.lower
|
|
130
|
+
)
|
|
131
|
+
assert (
|
|
132
|
+
request.periods_timespan.upper
|
|
133
|
+
== request.periods.latest("timespan__startswith").request.periods_timespan.upper
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def test_can_delete_draft_request(self, absence_request_factory, user):
|
|
137
|
+
request = absence_request_factory(status=AbsenceRequest.Status.DRAFT)
|
|
138
|
+
assert request.is_deletable_for_user(user)
|
|
139
|
+
|
|
140
|
+
def test_can_delete_future_pending_request(self, absence_request_factory, future_aware_datetime, user):
|
|
141
|
+
request = absence_request_factory(
|
|
142
|
+
status=AbsenceRequest.Status.PENDING,
|
|
143
|
+
period=TimestamptzRange(lower=future_aware_datetime, upper=future_aware_datetime + timedelta(days=2)),
|
|
144
|
+
)
|
|
145
|
+
assert request.is_deletable_for_user(user)
|
|
146
|
+
|
|
147
|
+
def test_can_delete_past_pending_request(self, absence_request_factory, past_aware_datetime, user_factory):
|
|
148
|
+
normal_user = user_factory.create()
|
|
149
|
+
admin_user = user_factory.create()
|
|
150
|
+
admin_user.user_permissions.add(Permission.objects.get(codename="administrate_absencerequest"))
|
|
151
|
+
request = absence_request_factory.create(
|
|
152
|
+
status=AbsenceRequest.Status.PENDING,
|
|
153
|
+
period=TimestamptzRange(lower=past_aware_datetime - timedelta(days=2), upper=past_aware_datetime),
|
|
154
|
+
)
|
|
155
|
+
assert not request.is_deletable_for_user(normal_user)
|
|
156
|
+
assert request.is_deletable_for_user(admin_user)
|
|
157
|
+
|
|
158
|
+
# Property checks and test
|
|
159
|
+
def test_total_hours(self, day_off_calendar, absence_request_periods_factory):
|
|
160
|
+
morning = day_off_calendar.default_periods.earliest("lower_time")
|
|
161
|
+
afternoon = day_off_calendar.default_periods.latest("lower_time")
|
|
162
|
+
p1 = absence_request_periods_factory.create(default_period=morning)
|
|
163
|
+
request = p1.request
|
|
164
|
+
absence_request_periods_factory.create(request=request, default_period=afternoon, date=p1.date)
|
|
165
|
+
assert request.total_hours == morning.total_hours + afternoon.total_hours
|
|
166
|
+
assert AbsenceRequest.objects.get(id=request.id)._total_hours == morning.total_hours + afternoon.total_hours
|
|
167
|
+
absence_request_periods_factory.create(
|
|
168
|
+
request=request, default_period=morning, date=(p1.date + pd.tseries.offsets.BDay(1)).date()
|
|
169
|
+
)
|
|
170
|
+
absence_request_periods_factory.create(
|
|
171
|
+
request=request, default_period=afternoon, date=(p1.date + pd.tseries.offsets.BDay(1)).date()
|
|
172
|
+
)
|
|
173
|
+
assert request.total_hours == (morning.total_hours + afternoon.total_hours) * 2
|
|
174
|
+
assert (
|
|
175
|
+
AbsenceRequest.objects.get(id=request.id)._total_hours == (morning.total_hours + afternoon.total_hours) * 2
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def test_total_vacation_hours(self, absence_request_factory, absence_request_type_factory):
|
|
179
|
+
time_off_request = absence_request_factory.create(
|
|
180
|
+
status=AbsenceRequest.Status.APPROVED, type=absence_request_type_factory.create(is_vacation=False)
|
|
181
|
+
)
|
|
182
|
+
vacation_request = absence_request_factory.create(
|
|
183
|
+
status=AbsenceRequest.Status.APPROVED, type=absence_request_type_factory.create(is_vacation=True)
|
|
184
|
+
)
|
|
185
|
+
unapprove_vacation_request = absence_request_factory.create(
|
|
186
|
+
type=absence_request_type_factory.create(is_vacation=True)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
assert time_off_request.total_vacation_hours == 0
|
|
190
|
+
assert AbsenceRequest.objects.get(id=time_off_request.id)._total_vacation_hours == 0
|
|
191
|
+
|
|
192
|
+
assert unapprove_vacation_request.total_vacation_hours == 0
|
|
193
|
+
assert AbsenceRequest.objects.get(id=unapprove_vacation_request.id)._total_vacation_hours == 0
|
|
194
|
+
|
|
195
|
+
assert vacation_request.total_vacation_hours
|
|
196
|
+
assert vacation_request.total_vacation_hours == vacation_request.periods.aggregate(s=Sum("_total_hours"))["s"]
|
|
197
|
+
assert (
|
|
198
|
+
AbsenceRequest.objects.get(id=vacation_request.id)._total_vacation_hours
|
|
199
|
+
== vacation_request.total_vacation_hours
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def test_total_hours_in_days(self, absence_request):
|
|
203
|
+
exp_res = (
|
|
204
|
+
absence_request.periods.aggregate(s=Sum("_total_hours"))["s"]
|
|
205
|
+
/ absence_request.employee.calendar.get_daily_hours()
|
|
206
|
+
)
|
|
207
|
+
assert exp_res
|
|
208
|
+
assert absence_request.total_hours_in_days == exp_res
|
|
209
|
+
assert AbsenceRequest.objects.get(id=absence_request.id)._total_hours_in_days == exp_res
|
|
210
|
+
|
|
211
|
+
def test_total_vacation_hours_in_days(self, absence_request_factory, absence_request_type_factory):
|
|
212
|
+
vacation_request = absence_request_factory.create(
|
|
213
|
+
status=AbsenceRequest.Status.APPROVED, type=absence_request_type_factory.create(is_vacation=True)
|
|
214
|
+
)
|
|
215
|
+
assert vacation_request.total_vacation_hours_in_days
|
|
216
|
+
assert (
|
|
217
|
+
vacation_request.total_vacation_hours_in_days
|
|
218
|
+
== vacation_request.periods.aggregate(s=Sum("_total_hours"))["s"]
|
|
219
|
+
/ vacation_request.employee.calendar.get_daily_hours()
|
|
220
|
+
)
|
|
221
|
+
assert (
|
|
222
|
+
AbsenceRequest.objects.get(id=vacation_request.id)._total_vacation_hours_in_days
|
|
223
|
+
== vacation_request.total_vacation_hours_in_days
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@patch("wbhuman_resources.models.absence.send_notification")
|
|
227
|
+
def test_notify_requester(self, mock_fct, absence_request):
|
|
228
|
+
title = fake.sentence()
|
|
229
|
+
message = fake.sentence()
|
|
230
|
+
absence_request.notify(title, message, to_requester=True)
|
|
231
|
+
mock_fct.assert_called_with(
|
|
232
|
+
code="wbhuman_resources.absencerequest.notify",
|
|
233
|
+
title=title,
|
|
234
|
+
body=message,
|
|
235
|
+
reverse_name="wbhuman_resources:absencerequest-detail",
|
|
236
|
+
reverse_args=[absence_request.id],
|
|
237
|
+
user=absence_request.employee.profile.user_account,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
@patch("wbhuman_resources.models.absence.send_notification")
|
|
241
|
+
def test_notify_managers(self, mock_fct, absence_request, authenticated_person_factory):
|
|
242
|
+
direct_manager = authenticated_person_factory.create()
|
|
243
|
+
absence_request.employee.direct_manager = direct_manager
|
|
244
|
+
absence_request.employee.save()
|
|
245
|
+
|
|
246
|
+
# add a general manager
|
|
247
|
+
general_manager = authenticated_person_factory.create()
|
|
248
|
+
general_manager.user_account.user_permissions.add(
|
|
249
|
+
Permission.objects.get(codename="administrate_employeehumanresource")
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
absence_request.refresh_from_db()
|
|
253
|
+
|
|
254
|
+
title = fake.sentence()
|
|
255
|
+
message = fake.sentence()
|
|
256
|
+
absence_request.notify(title, message, to_requester=False, to_manager=True)
|
|
257
|
+
mock_fct.assert_any_call(
|
|
258
|
+
code="wbhuman_resources.absencerequest.notify",
|
|
259
|
+
title=title,
|
|
260
|
+
body=message,
|
|
261
|
+
reverse_name="wbhuman_resources:absencerequest-detail",
|
|
262
|
+
reverse_args=[absence_request.id],
|
|
263
|
+
user=general_manager.user_account,
|
|
264
|
+
)
|
|
265
|
+
mock_fct.assert_any_call(
|
|
266
|
+
code="wbhuman_resources.absencerequest.notify",
|
|
267
|
+
title=title,
|
|
268
|
+
body=message,
|
|
269
|
+
reverse_name="wbhuman_resources:absencerequest-detail",
|
|
270
|
+
reverse_args=[absence_request.id],
|
|
271
|
+
user=direct_manager.user_account,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
@patch("wbhuman_resources.models.absence.send_notification")
|
|
275
|
+
def test_notify_extra_notify_user(self, mock_fct, absence_request, user):
|
|
276
|
+
# We create a user, add it to a test group and add this group to the absence request type "extra_notify_group"
|
|
277
|
+
group = Group.objects.create(name="test")
|
|
278
|
+
user.groups.add(group)
|
|
279
|
+
absence_request.type.extra_notify_groups.add(group)
|
|
280
|
+
absence_request.refresh_from_db()
|
|
281
|
+
|
|
282
|
+
title = fake.sentence()
|
|
283
|
+
message = fake.sentence()
|
|
284
|
+
absence_request.notify(title, message, to_requester=False, to_manager=True)
|
|
285
|
+
mock_fct.assert_called_with(
|
|
286
|
+
code="wbhuman_resources.absencerequest.notify",
|
|
287
|
+
title=title,
|
|
288
|
+
body=message,
|
|
289
|
+
reverse_name="wbhuman_resources:absencerequest-detail",
|
|
290
|
+
reverse_args=[absence_request.id],
|
|
291
|
+
user=user,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@pytest.mark.django_db
|
|
296
|
+
class TestAbsenceRequestType:
|
|
297
|
+
def test_get_choices(self, absence_request_type_factory):
|
|
298
|
+
t1 = absence_request_type_factory.create()
|
|
299
|
+
t2 = absence_request_type_factory.create()
|
|
300
|
+
assert AbsenceRequestType.get_choices() == [(t1.id, t1.title), (t2.id, t2.title)]
|
|
301
|
+
|
|
302
|
+
def test_validate_country_needed_but_not_specified(self, absence_request_type_factory):
|
|
303
|
+
absence_request_type = absence_request_type_factory.create(is_country_necessary=True)
|
|
304
|
+
with pytest.raises(ValueError):
|
|
305
|
+
absence_request_type.validate_country(None)
|
|
306
|
+
|
|
307
|
+
def test_validate_country_specified_and_allowed(self, absence_request_type_factory, country):
|
|
308
|
+
absence_request_type = absence_request_type_factory.create(
|
|
309
|
+
is_country_necessary=True,
|
|
310
|
+
) # test that the post_save automatically appends the newly created country to the list of allowed countries
|
|
311
|
+
assert absence_request_type.validate_country(country)
|
|
312
|
+
|
|
313
|
+
def test_validate_country_specified_but_not_allowed(self, absence_request_type_factory, country):
|
|
314
|
+
absence_request_type = absence_request_type_factory.create(is_country_necessary=True)
|
|
315
|
+
absence_request_type.crossborder_countries.clear()
|
|
316
|
+
with pytest.raises(ValueError):
|
|
317
|
+
absence_request_type.validate_country(country)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@pytest.mark.django_db
|
|
321
|
+
class TestAbsenceRequestPeriod:
|
|
322
|
+
def test_total_hours(self, absence_request_periods):
|
|
323
|
+
exp_res = absence_request_periods.default_period.total_hours
|
|
324
|
+
assert exp_res
|
|
325
|
+
assert absence_request_periods.total_hours == exp_res
|
|
326
|
+
assert AbsenceRequestPeriods.objects.get(id=absence_request_periods.id)._total_hours == exp_res
|
|
327
|
+
|
|
328
|
+
@pytest.mark.parametrize("past_date,future_date", [(fake.past_date(), fake.future_date())])
|
|
329
|
+
def test_previous_vacation_period(self, absence_request_periods_factory, past_date, future_date):
|
|
330
|
+
current = absence_request_periods_factory.create(date=date.today())
|
|
331
|
+
past = absence_request_periods_factory.create(employee=current.employee, date=past_date)
|
|
332
|
+
future = absence_request_periods_factory.create(employee=current.employee, date=future_date)
|
|
333
|
+
AbsenceRequest.objects.update(status=AbsenceRequest.Status.APPROVED, type=current.request.type)
|
|
334
|
+
assert current.previous_period == past
|
|
335
|
+
assert future.previous_period == current
|
|
336
|
+
assert past.previous_period is None
|
|
337
|
+
|
|
338
|
+
@pytest.mark.parametrize("test_date", [(fake.date_this_year())])
|
|
339
|
+
def test_get_periods_as_df(
|
|
340
|
+
self, day_off_calendar, employee_human_resource_factory, absence_request_periods_factory, test_date
|
|
341
|
+
):
|
|
342
|
+
employee1 = employee_human_resource_factory.create(calendar=day_off_calendar, is_active=True)
|
|
343
|
+
employee2 = employee_human_resource_factory.create(
|
|
344
|
+
calendar=day_off_calendar, is_active=False
|
|
345
|
+
) # Expect this employee to not be present
|
|
346
|
+
p1 = absence_request_periods_factory.create(employee=employee1, date=test_date)
|
|
347
|
+
p2 = absence_request_periods_factory.create(employee=employee2, date=test_date)
|
|
348
|
+
p3 = absence_request_periods_factory.create(employee=employee1, date=test_date - timedelta(days=1))
|
|
349
|
+
res = AbsenceRequestPeriods.get_periods_as_df(
|
|
350
|
+
test_date, test_date + timedelta(days=1), employee__is_active=True
|
|
351
|
+
)
|
|
352
|
+
assert res.shape == (1, 5)
|
|
353
|
+
res = res.set_index(["employee", "period", "date"])
|
|
354
|
+
assert res.loc[(employee1.id, p1.default_period.id, test_date), :].values.tolist() == [
|
|
355
|
+
p1.request.type.title,
|
|
356
|
+
p1.request.status,
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
with pytest.raises(KeyError):
|
|
360
|
+
assert res.loc[(employee2.id, p2.default_period.id, test_date), :].values.tolist() == [
|
|
361
|
+
p2.request.type.title,
|
|
362
|
+
p2.request.status,
|
|
363
|
+
]
|
|
364
|
+
assert res.loc[(employee1.id, p3.period.id, test_date - timedelta(days=1)), :]
|
|
365
|
+
|
|
366
|
+
@pytest.mark.parametrize("year_str", [(fake.year())])
|
|
367
|
+
def test_assign_balance(
|
|
368
|
+
self, employee_human_resource, absence_request_periods_factory, employee_year_balance_factory, year_str
|
|
369
|
+
):
|
|
370
|
+
year = int(year_str)
|
|
371
|
+
previous_balance = employee_year_balance_factory.create(
|
|
372
|
+
employee=employee_human_resource, extra_balance=0, year=year - 1
|
|
373
|
+
)
|
|
374
|
+
current_balance = employee_year_balance_factory.create(
|
|
375
|
+
employee=employee_human_resource, extra_balance=0, year=year
|
|
376
|
+
)
|
|
377
|
+
next_balance = employee_year_balance_factory.create(
|
|
378
|
+
employee=employee_human_resource, extra_balance=0, year=year + 1
|
|
379
|
+
)
|
|
380
|
+
BalanceHourlyAllowance.objects.update(
|
|
381
|
+
hourly_allowance=4
|
|
382
|
+
) # Update all created balance allowance with 4 crdits (corresponds to a period)
|
|
383
|
+
|
|
384
|
+
p1 = absence_request_periods_factory.create(
|
|
385
|
+
balance=None, employee=employee_human_resource, date=fake.date_between(date(year, 1, 1), date(year, 3, 31))
|
|
386
|
+
)
|
|
387
|
+
p1.assign_balance()
|
|
388
|
+
assert p1.balance == previous_balance
|
|
389
|
+
|
|
390
|
+
p2 = absence_request_periods_factory.create(
|
|
391
|
+
balance=None,
|
|
392
|
+
employee=employee_human_resource,
|
|
393
|
+
date=fake.date_between(date(year, 3, 31), date(year, 6, 30)),
|
|
394
|
+
)
|
|
395
|
+
p2.assign_balance()
|
|
396
|
+
assert p2.balance == current_balance
|
|
397
|
+
|
|
398
|
+
p3 = absence_request_periods_factory.create(
|
|
399
|
+
balance=None,
|
|
400
|
+
employee=employee_human_resource,
|
|
401
|
+
date=fake.date_between(date(year, 6, 30), date(year, 12, 31)),
|
|
402
|
+
)
|
|
403
|
+
p3.assign_balance()
|
|
404
|
+
assert p3.balance == next_balance
|
|
405
|
+
|
|
406
|
+
p4 = absence_request_periods_factory.create( # request but no balance left
|
|
407
|
+
balance=None,
|
|
408
|
+
employee=employee_human_resource,
|
|
409
|
+
date=fake.date_between(date(year, 1, 1), date(year, 12, 31)),
|
|
410
|
+
)
|
|
411
|
+
p4.assign_balance()
|
|
412
|
+
assert p4.balance.year == year + 2
|
|
413
|
+
|
|
414
|
+
@pytest.mark.parametrize("year_str", [(fake.year())])
|
|
415
|
+
def test_assign_balance_with_expired_balance(
|
|
416
|
+
self, employee_human_resource, absence_request_periods_factory, employee_year_balance_factory, year_str
|
|
417
|
+
):
|
|
418
|
+
year = int(year_str)
|
|
419
|
+
expiration_date = get_previous_year_balance_expiration_date(year)
|
|
420
|
+
|
|
421
|
+
for y in range(year, expiration_date.year - 1):
|
|
422
|
+
employee_year_balance_factory.create(employee=employee_human_resource, extra_balance=0, year=y)
|
|
423
|
+
BalanceHourlyAllowance.objects.filter(balance__year=year).update(
|
|
424
|
+
hourly_allowance=4
|
|
425
|
+
) # Update all created balance allowance with 4 crdits (corresponds to a period)
|
|
426
|
+
BalanceHourlyAllowance.objects.exclude(balance__year=year).update(
|
|
427
|
+
hourly_allowance=0
|
|
428
|
+
) # and set anything else to 0
|
|
429
|
+
p1 = absence_request_periods_factory.create(
|
|
430
|
+
balance=None,
|
|
431
|
+
employee=employee_human_resource,
|
|
432
|
+
date=fake.date_between(expiration_date, date(expiration_date.year, 12, 31)),
|
|
433
|
+
)
|
|
434
|
+
p1.assign_balance()
|
|
435
|
+
assert p1.balance is not None
|
|
436
|
+
assert p1.balance.year == year + 1
|
|
437
|
+
assert p1.balance.balance == 0
|
|
438
|
+
|
|
439
|
+
def test_no_vacation_or_approved_request_has_no_balance(self, absence_request_periods):
|
|
440
|
+
AbsenceRequest.objects.update(status=AbsenceRequest.Status.DRAFT)
|
|
441
|
+
AbsenceRequestType.objects.update(is_vacation=False)
|
|
442
|
+
AbsenceRequestPeriods.objects.update(balance=None)
|
|
443
|
+
absence_request_periods.refresh_from_db()
|
|
444
|
+
absence_request_periods.assign_balance()
|
|
445
|
+
assert absence_request_periods.balance is None
|
|
446
|
+
|
|
447
|
+
def test_get_consecutive_hours_count(
|
|
448
|
+
self,
|
|
449
|
+
absence_request_periods_factory,
|
|
450
|
+
employee_human_resource,
|
|
451
|
+
employee_weekly_off_periods_factory,
|
|
452
|
+
day_off_factory,
|
|
453
|
+
):
|
|
454
|
+
morning = employee_human_resource.calendar.default_periods.earliest("lower_time")
|
|
455
|
+
afternoon = employee_human_resource.calendar.default_periods.latest("lower_time")
|
|
456
|
+
hours_per_period = 4
|
|
457
|
+
p1 = absence_request_periods_factory.create(employee=employee_human_resource, default_period=morning)
|
|
458
|
+
assert p1.consecutive_hours_count == hours_per_period
|
|
459
|
+
p2 = absence_request_periods_factory.create(
|
|
460
|
+
date=p1.date, employee=employee_human_resource, default_period=afternoon
|
|
461
|
+
) # Straight next period, counter should be incremented
|
|
462
|
+
assert p2.consecutive_hours_count == hours_per_period * 2
|
|
463
|
+
employee_weekly_off_periods_factory.create(
|
|
464
|
+
period=morning, weekday=(p1.date.weekday() + 1) % 6, employee=employee_human_resource
|
|
465
|
+
)
|
|
466
|
+
p3 = absence_request_periods_factory.create(
|
|
467
|
+
date=p1.date + timedelta(days=1), employee=employee_human_resource, default_period=afternoon
|
|
468
|
+
) # Expected to keep incremeting counter because the previous employee's weekly day off period is jumped
|
|
469
|
+
assert p3.consecutive_hours_count == hours_per_period * 3
|
|
470
|
+
day_off_factory.create(calendar=employee_human_resource.calendar, date=p1.date + timedelta(days=2))
|
|
471
|
+
p4 = absence_request_periods_factory.create(
|
|
472
|
+
date=p1.date + timedelta(days=3), employee=employee_human_resource, default_period=morning
|
|
473
|
+
) # Expected to keep incremeting counter because the previous day off is jumped
|
|
474
|
+
assert p4.consecutive_hours_count == hours_per_period * 4
|
|
475
|
+
p5 = absence_request_periods_factory.create(
|
|
476
|
+
date=p1.date + timedelta(days=4), employee=employee_human_resource, default_period=morning
|
|
477
|
+
)
|
|
478
|
+
assert p5.consecutive_hours_count == hours_per_period # Expect reset of counter
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import zoneinfo
|
|
2
|
+
from datetime import datetime, time, timedelta
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from django.db.models import Q
|
|
7
|
+
from django.utils.timezone import make_aware
|
|
8
|
+
from faker import Faker
|
|
9
|
+
from psycopg.types.range import TimestamptzRange
|
|
10
|
+
|
|
11
|
+
from wbhuman_resources.models.calendars import (
|
|
12
|
+
DayOff,
|
|
13
|
+
InvalidDayOffCalendarResourceError,
|
|
14
|
+
)
|
|
15
|
+
from wbhuman_resources.models.employee import EmployeeHumanResource
|
|
16
|
+
|
|
17
|
+
fake = Faker()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.django_db
|
|
21
|
+
class TestDayOffCalendar:
|
|
22
|
+
def test_get_period_start_choices(self, day_off_calendar):
|
|
23
|
+
assert day_off_calendar.get_period_start_choices() == ["09:00:00", "14:00:00"]
|
|
24
|
+
|
|
25
|
+
def test_get_period_end_choices(self, day_off_calendar):
|
|
26
|
+
assert day_off_calendar.get_period_end_choices() == ["13:00:00", "18:00:00"]
|
|
27
|
+
|
|
28
|
+
@pytest.mark.parametrize("val_date", [(fake.date_this_decade())])
|
|
29
|
+
def test_create_public_holidays_sanitize_resource(self, day_off_calendar_factory, val_date):
|
|
30
|
+
with pytest.raises(InvalidDayOffCalendarResourceError):
|
|
31
|
+
calendar = day_off_calendar_factory.create(resource="eurasia.China")
|
|
32
|
+
calendar.create_public_holidays(val_date.year)
|
|
33
|
+
with pytest.raises(InvalidDayOffCalendarResourceError):
|
|
34
|
+
calendar = day_off_calendar_factory.create(resource="europe.Listenbourg")
|
|
35
|
+
calendar.create_public_holidays(val_date.year)
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize(
|
|
38
|
+
"val_date,continent,region",
|
|
39
|
+
[
|
|
40
|
+
(fake.date_this_decade(), "europe", "Berlin"),
|
|
41
|
+
(fake.date_this_decade(), "europe", "Switzerland"),
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
def test_create_public_holidays_valid_resource(self, day_off_calendar_factory, val_date, continent, region):
|
|
45
|
+
calendar = day_off_calendar_factory.create(resource=f"{continent}.{region}")
|
|
46
|
+
calendar.create_public_holidays(val_date.year)
|
|
47
|
+
workalendar = import_module(f"workalendar.{continent}")
|
|
48
|
+
cal = getattr(workalendar, region)()
|
|
49
|
+
for _d, _ in cal.holidays(val_date.year):
|
|
50
|
+
assert DayOff.objects.filter(
|
|
51
|
+
date=_d,
|
|
52
|
+
calendar=calendar,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def test_get_day_off_per_employee_df(self, day_off_calendar, employee_human_resource_factory, day_off_factory):
|
|
56
|
+
employee1 = employee_human_resource_factory.create()
|
|
57
|
+
employee2 = employee_human_resource_factory.create()
|
|
58
|
+
employee_human_resource_factory.create()
|
|
59
|
+
base_day_off = day_off_factory.create()
|
|
60
|
+
period1 = day_off_calendar.default_periods.first()
|
|
61
|
+
period2 = day_off_calendar.default_periods.last()
|
|
62
|
+
day_off_factory.create(date=base_day_off.date - timedelta(days=1)) # create left_outside_day_off
|
|
63
|
+
day_off_factory.create(date=base_day_off.date + timedelta(days=1)) # create right_outside_day_off
|
|
64
|
+
res = (
|
|
65
|
+
day_off_calendar.get_day_off_per_employee_df(
|
|
66
|
+
base_day_off.date,
|
|
67
|
+
base_day_off.date,
|
|
68
|
+
EmployeeHumanResource.objects.filter(Q(id=employee1.id) | Q(id=employee2.id)),
|
|
69
|
+
)
|
|
70
|
+
.set_index(["employee", "period"])
|
|
71
|
+
.to_dict("index")
|
|
72
|
+
)
|
|
73
|
+
assert res == {
|
|
74
|
+
(employee1.id, period1.id): {"date": base_day_off.date, "type": "Holiday", "status": "APPROVED"},
|
|
75
|
+
(employee1.id, period2.id): {"date": base_day_off.date, "type": "Holiday", "status": "APPROVED"},
|
|
76
|
+
(employee2.id, period1.id): {"date": base_day_off.date, "type": "Holiday", "status": "APPROVED"},
|
|
77
|
+
(employee2.id, period2.id): {"date": base_day_off.date, "type": "Holiday", "status": "APPROVED"},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@pytest.mark.parametrize("h1,h2", [(fake.pyint(min_value=1), fake.pyint(min_value=1))])
|
|
81
|
+
def test_get_daily_hours(self, day_off_calendar_without_period, default_daily_period_factory, h1, h2):
|
|
82
|
+
default_daily_period_factory.create(
|
|
83
|
+
calendar=day_off_calendar_without_period,
|
|
84
|
+
total_hours=h1,
|
|
85
|
+
lower_time=time(9, 0, 0),
|
|
86
|
+
upper_time=time(13, 0, 0),
|
|
87
|
+
)
|
|
88
|
+
default_daily_period_factory.create(
|
|
89
|
+
calendar=day_off_calendar_without_period,
|
|
90
|
+
total_hours=h2,
|
|
91
|
+
lower_time=time(14, 0, 0),
|
|
92
|
+
upper_time=time(18, 0, 0),
|
|
93
|
+
)
|
|
94
|
+
assert day_off_calendar_without_period.get_daily_hours() == h1 + h2
|
|
95
|
+
|
|
96
|
+
@pytest.mark.parametrize(
|
|
97
|
+
"ranges, hour_start, expected_res",
|
|
98
|
+
[
|
|
99
|
+
(
|
|
100
|
+
[(time(9, 0, 0), time(13, 0, 0)), (time(14, 0, 0), time(18, 0, 0))],
|
|
101
|
+
time(0, 0),
|
|
102
|
+
[(time(0, 0), time(8, 59)), (time(13, 1), time(13, 59)), (time(18, 1), time(23, 59))],
|
|
103
|
+
),
|
|
104
|
+
(
|
|
105
|
+
[(time(9, 0, 0), time(13, 0, 0)), (time(14, 0, 0), time(18, 0, 0))],
|
|
106
|
+
time(4, 0),
|
|
107
|
+
[(time(4, 0), time(8, 59)), (time(13, 1), time(13, 59)), (time(18, 1), time(3, 59))],
|
|
108
|
+
),
|
|
109
|
+
(
|
|
110
|
+
[(time(9, 0, 0), time(13, 0, 0)), (time(14, 0, 0), time(18, 0, 0))],
|
|
111
|
+
time(15, 0),
|
|
112
|
+
[(time(18, 1), time(8, 59)), (time(13, 1), time(13, 59))],
|
|
113
|
+
),
|
|
114
|
+
(
|
|
115
|
+
[(time(9, 0, 0), time(13, 0, 0)), (time(14, 0, 0), time(18, 0, 0))],
|
|
116
|
+
time(20, 0),
|
|
117
|
+
[(time(20, 0), time(8, 59)), (time(13, 1), time(13, 59)), (time(18, 1), time(19, 59))],
|
|
118
|
+
),
|
|
119
|
+
],
|
|
120
|
+
)
|
|
121
|
+
def test_get_unworked_time_range(
|
|
122
|
+
self, day_off_calendar_without_period, default_daily_period_factory, ranges, hour_start, expected_res
|
|
123
|
+
):
|
|
124
|
+
for lower, upper in ranges:
|
|
125
|
+
default_daily_period_factory.create(
|
|
126
|
+
calendar=day_off_calendar_without_period, lower_time=lower, upper_time=upper
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
assert list(day_off_calendar_without_period.get_unworked_time_range(hour_start)) == expected_res
|
|
130
|
+
|
|
131
|
+
@pytest.mark.parametrize(
|
|
132
|
+
"unnormalized_lower_datetime, unnormalized_upper_datetime, expected_lower_datetime, expected_upper_datetime",
|
|
133
|
+
[
|
|
134
|
+
(datetime(2023, 1, 1, 8), datetime(2023, 1, 1, 20), datetime(2023, 1, 1, 9), datetime(2023, 1, 1, 18)),
|
|
135
|
+
(datetime(2023, 1, 1, 15), datetime(2023, 1, 1, 23), datetime(2023, 1, 1, 14), datetime(2023, 1, 1, 18)),
|
|
136
|
+
(
|
|
137
|
+
datetime(2023, 1, 1, 2, 3, 2),
|
|
138
|
+
datetime(2023, 1, 1, 23, 5, 6),
|
|
139
|
+
datetime(2023, 1, 1, 9),
|
|
140
|
+
datetime(2023, 1, 1, 18),
|
|
141
|
+
),
|
|
142
|
+
],
|
|
143
|
+
)
|
|
144
|
+
def test_normalize_period(
|
|
145
|
+
self,
|
|
146
|
+
day_off_calendar,
|
|
147
|
+
unnormalized_lower_datetime,
|
|
148
|
+
unnormalized_upper_datetime,
|
|
149
|
+
expected_lower_datetime,
|
|
150
|
+
expected_upper_datetime,
|
|
151
|
+
):
|
|
152
|
+
normalized_period = day_off_calendar.normalize_period(
|
|
153
|
+
TimestamptzRange(
|
|
154
|
+
lower=make_aware(unnormalized_lower_datetime, day_off_calendar.timezone),
|
|
155
|
+
upper=make_aware(unnormalized_upper_datetime, day_off_calendar.timezone),
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
assert normalized_period.lower == make_aware(expected_lower_datetime, day_off_calendar.timezone)
|
|
159
|
+
assert normalized_period.upper == make_aware(expected_upper_datetime, day_off_calendar.timezone)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@pytest.mark.django_db
|
|
163
|
+
class TestDayOff:
|
|
164
|
+
@pytest.mark.parametrize(
|
|
165
|
+
"timezone_str, lower_time, upper_time",
|
|
166
|
+
[
|
|
167
|
+
("Pacific/Kwajalein", time(9, 0, 0), time(18, 0, 0)),
|
|
168
|
+
("Europe/Berlin", time(9, 0, 0), time(18, 0, 0)),
|
|
169
|
+
("Europe/Berlin", time(15, 0, 0), time(16, 0, 0)),
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
def test_get_timespan(
|
|
173
|
+
self,
|
|
174
|
+
base_day_off_calendar_factory,
|
|
175
|
+
default_daily_period_factory,
|
|
176
|
+
day_off_factory,
|
|
177
|
+
timezone_str,
|
|
178
|
+
lower_time,
|
|
179
|
+
upper_time,
|
|
180
|
+
):
|
|
181
|
+
timezone = zoneinfo.ZoneInfo(timezone_str)
|
|
182
|
+
calendar = base_day_off_calendar_factory.create(timezone=timezone)
|
|
183
|
+
default_daily_period_factory.create(calendar=calendar, lower_time=lower_time, upper_time=upper_time)
|
|
184
|
+
day_off = day_off_factory.create(calendar=calendar)
|
|
185
|
+
assert day_off.period.lower == datetime.combine(day_off.date, lower_time, tzinfo=timezone).astimezone(
|
|
186
|
+
zoneinfo.ZoneInfo("UTC")
|
|
187
|
+
)
|
|
188
|
+
assert day_off.period.upper == datetime.combine(day_off.date, upper_time, tzinfo=timezone).astimezone(
|
|
189
|
+
zoneinfo.ZoneInfo("UTC")
|
|
190
|
+
)
|
|
191
|
+
#
|
|
192
|
+
# timespan = day_off.get_timespan(as_utc=False)
|
|
193
|
+
# assert timespan.lower == datetime.combine(day_off.date, lower_time, tzinfo=timezone)
|
|
194
|
+
# assert timespan.upper == datetime.combine(day_off.date, upper_time, tzinfo=timezone)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pytest.mark.django_db
|
|
198
|
+
class TestDefaultDailyPeriod:
|
|
199
|
+
@pytest.mark.parametrize("val_date", [fake.date_object()])
|
|
200
|
+
def test_get_lower_datetime(self, default_daily_period, val_date):
|
|
201
|
+
assert default_daily_period.get_lower_datetime(val_date) == datetime.combine(
|
|
202
|
+
val_date, default_daily_period.lower_time, tzinfo=default_daily_period.calendar.timezone
|
|
203
|
+
).astimezone(zoneinfo.ZoneInfo("UTC"))
|
|
204
|
+
|
|
205
|
+
@pytest.mark.parametrize("val_date", [fake.date_object()])
|
|
206
|
+
def test_get_upper_datetime(self, default_daily_period, val_date):
|
|
207
|
+
assert default_daily_period.get_upper_datetime(val_date) == datetime.combine(
|
|
208
|
+
val_date, default_daily_period.upper_time, tzinfo=default_daily_period.calendar.timezone
|
|
209
|
+
).astimezone(zoneinfo.ZoneInfo("UTC"))
|