wbhuman_resources 2.2.1__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 +22 -0
- wbhuman_resources/dynamic_preferences_registry.py +118 -0
- wbhuman_resources/factories/__init__.py +38 -0
- wbhuman_resources/factories/absence.py +108 -0
- wbhuman_resources/factories/calendars.py +59 -0
- wbhuman_resources/factories/employee.py +79 -0
- wbhuman_resources/factories/kpi.py +154 -0
- wbhuman_resources/filters/__init__.py +20 -0
- wbhuman_resources/filters/absence.py +108 -0
- wbhuman_resources/filters/absence_graphs.py +84 -0
- wbhuman_resources/filters/calendars.py +28 -0
- wbhuman_resources/filters/employee.py +80 -0
- wbhuman_resources/filters/kpi.py +34 -0
- wbhuman_resources/filters/review.py +133 -0
- wbhuman_resources/filters/signals.py +26 -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 +889 -0
- wbhuman_resources/models/calendars.py +370 -0
- wbhuman_resources/models/employee.py +1211 -0
- wbhuman_resources/models/kpi.py +199 -0
- wbhuman_resources/models/preferences.py +39 -0
- wbhuman_resources/models/review.py +978 -0
- wbhuman_resources/permissions/__init__.py +0 -0
- wbhuman_resources/permissions/backend.py +25 -0
- wbhuman_resources/serializers/__init__.py +49 -0
- wbhuman_resources/serializers/absence.py +296 -0
- wbhuman_resources/serializers/calendars.py +71 -0
- wbhuman_resources/serializers/employee.py +266 -0
- wbhuman_resources/serializers/kpi.py +79 -0
- wbhuman_resources/serializers/review.py +414 -0
- wbhuman_resources/tasks.py +189 -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 +477 -0
- wbhuman_resources/tests/models/test_calendars.py +208 -0
- wbhuman_resources/tests/models/test_employees.py +501 -0
- wbhuman_resources/tests/models/test_review.py +102 -0
- wbhuman_resources/tests/models/test_utils.py +109 -0
- wbhuman_resources/tests/signals.py +107 -0
- wbhuman_resources/tests/test_permission.py +63 -0
- wbhuman_resources/tests/test_tasks.py +73 -0
- wbhuman_resources/urls.py +220 -0
- wbhuman_resources/utils.py +40 -0
- wbhuman_resources/viewsets/__init__.py +61 -0
- wbhuman_resources/viewsets/absence.py +327 -0
- wbhuman_resources/viewsets/absence_charts.py +327 -0
- wbhuman_resources/viewsets/buttons/__init__.py +7 -0
- wbhuman_resources/viewsets/buttons/absence.py +31 -0
- wbhuman_resources/viewsets/buttons/employee.py +43 -0
- wbhuman_resources/viewsets/buttons/kpis.py +16 -0
- wbhuman_resources/viewsets/buttons/review.py +194 -0
- wbhuman_resources/viewsets/calendars.py +102 -0
- wbhuman_resources/viewsets/display/__init__.py +39 -0
- wbhuman_resources/viewsets/display/absence.py +344 -0
- wbhuman_resources/viewsets/display/calendars.py +84 -0
- wbhuman_resources/viewsets/display/employee.py +253 -0
- wbhuman_resources/viewsets/display/kpis.py +91 -0
- wbhuman_resources/viewsets/display/review.py +427 -0
- wbhuman_resources/viewsets/employee.py +209 -0
- wbhuman_resources/viewsets/endpoints/__init__.py +42 -0
- wbhuman_resources/viewsets/endpoints/absence.py +74 -0
- wbhuman_resources/viewsets/endpoints/calendars.py +18 -0
- wbhuman_resources/viewsets/endpoints/employee.py +57 -0
- wbhuman_resources/viewsets/endpoints/kpis.py +58 -0
- wbhuman_resources/viewsets/endpoints/review.py +203 -0
- wbhuman_resources/viewsets/kpi.py +279 -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 +13 -0
- wbhuman_resources/viewsets/review.py +836 -0
- wbhuman_resources/viewsets/titles/__init__.py +18 -0
- wbhuman_resources/viewsets/titles/absence.py +29 -0
- wbhuman_resources/viewsets/titles/employee.py +18 -0
- wbhuman_resources/viewsets/titles/kpis.py +14 -0
- wbhuman_resources/viewsets/titles/review.py +61 -0
- wbhuman_resources/viewsets/utils.py +28 -0
- wbhuman_resources-2.2.1.dist-info/METADATA +9 -0
- wbhuman_resources-2.2.1.dist-info/RECORD +102 -0
- wbhuman_resources-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from pandas.tseries.offsets import BDay
|
|
6
|
+
from psycopg.types.range import TimestamptzRange
|
|
7
|
+
from wbcore.contrib.icons import WBIcon
|
|
8
|
+
from wbhuman_resources.models import (
|
|
9
|
+
AbsenceRequest,
|
|
10
|
+
AbsenceRequestPeriods,
|
|
11
|
+
AbsenceRequestType,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .calendars import DefaultDailyPeriodFactory
|
|
15
|
+
from .employee import EmployeeYearBalanceFactory
|
|
16
|
+
|
|
17
|
+
fake = Faker()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AbsenceRequestTypeFactory(factory.django.DjangoModelFactory):
|
|
21
|
+
class Meta:
|
|
22
|
+
model = AbsenceRequestType
|
|
23
|
+
django_get_or_create = ["title"]
|
|
24
|
+
|
|
25
|
+
title = factory.Faker("text", max_nb_chars=64)
|
|
26
|
+
icon = factory.Iterator(WBIcon.values)
|
|
27
|
+
is_vacation = factory.Faker("boolean")
|
|
28
|
+
is_timeoff = factory.Faker("boolean")
|
|
29
|
+
is_extensible = factory.Faker("boolean")
|
|
30
|
+
auto_approve = False
|
|
31
|
+
days_in_advance = factory.Faker("pyint", min_value=0, max_value=5)
|
|
32
|
+
|
|
33
|
+
@factory.post_generation
|
|
34
|
+
def crossborder_countries(self, create, extracted, **kwargs):
|
|
35
|
+
if not create:
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
if extracted:
|
|
39
|
+
for country in extracted:
|
|
40
|
+
self.crossborder_countries.add(country)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_random_period(calendar):
|
|
44
|
+
lower = Faker().future_datetime() + BDay(0)
|
|
45
|
+
upper = lower + BDay(random.randint(1, 7))
|
|
46
|
+
return TimestamptzRange(
|
|
47
|
+
lower=lower.to_pydatetime().astimezone(calendar.timezone),
|
|
48
|
+
upper=upper.to_pydatetime().astimezone(calendar.timezone),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class VacationTypeFactory(AbsenceRequestTypeFactory):
|
|
53
|
+
title = "Vacation"
|
|
54
|
+
is_vacation = True
|
|
55
|
+
is_timeoff = True
|
|
56
|
+
is_extensible = False
|
|
57
|
+
auto_approve = False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TimeOffTypeFactory(AbsenceRequestTypeFactory):
|
|
61
|
+
title = "TimeOff"
|
|
62
|
+
is_vacation = False
|
|
63
|
+
is_timeoff = True
|
|
64
|
+
is_extensible = True
|
|
65
|
+
auto_approve = True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AbsenceRequestFactory(factory.django.DjangoModelFactory):
|
|
69
|
+
class Meta:
|
|
70
|
+
model = AbsenceRequest
|
|
71
|
+
|
|
72
|
+
period = factory.LazyAttribute(lambda o: _get_random_period(o.employee.calendar))
|
|
73
|
+
attachment = factory.django.FileField()
|
|
74
|
+
type = factory.SubFactory("wbhuman_resources.factories.AbsenceRequestTypeFactory")
|
|
75
|
+
employee = factory.SubFactory("wbhuman_resources.factories.EmployeeHumanResourceFactory")
|
|
76
|
+
notes = factory.Faker("text")
|
|
77
|
+
reason = factory.Faker("text")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class VacationRequestFactory(AbsenceRequestFactory):
|
|
81
|
+
type = factory.SubFactory(VacationTypeFactory)
|
|
82
|
+
status = AbsenceRequest.Status.APPROVED
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TimeOffRequestFactory(AbsenceRequestFactory):
|
|
86
|
+
type = factory.SubFactory(TimeOffTypeFactory)
|
|
87
|
+
status = AbsenceRequest.Status.APPROVED
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AbsenceRequestPeriodsFactory(factory.django.DjangoModelFactory):
|
|
91
|
+
class Meta:
|
|
92
|
+
model = AbsenceRequestPeriods
|
|
93
|
+
django_get_or_create = ["employee", "default_period", "date"]
|
|
94
|
+
|
|
95
|
+
date = factory.LazyAttribute(lambda o: (fake.date_object() + BDay(0)).date())
|
|
96
|
+
employee = factory.SubFactory("wbhuman_resources.factories.EmployeeHumanResourceFactory")
|
|
97
|
+
default_period = factory.LazyAttribute(lambda o: DefaultDailyPeriodFactory.create(calendar=o.employee.calendar))
|
|
98
|
+
request = factory.LazyAttribute(
|
|
99
|
+
lambda o: AbsenceRequestFactory.create(
|
|
100
|
+
status=AbsenceRequest.Status.APPROVED,
|
|
101
|
+
type=VacationTypeFactory.create(),
|
|
102
|
+
employee=o.employee,
|
|
103
|
+
period=TimestamptzRange(
|
|
104
|
+
lower=o.default_period.get_lower_datetime(o.date), upper=o.default_period.get_upper_datetime(o.date)
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
balance = factory.LazyAttribute(lambda o: EmployeeYearBalanceFactory.create(year=o.date.year, employee=o.employee))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import zoneinfo
|
|
2
|
+
from datetime import time
|
|
3
|
+
|
|
4
|
+
import factory
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from wbhuman_resources.models.calendars import (
|
|
7
|
+
DayOff,
|
|
8
|
+
DayOffCalendar,
|
|
9
|
+
DefaultDailyPeriod,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
fake = Faker()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseDayOffCalendarFactory(factory.django.DjangoModelFactory):
|
|
16
|
+
title = "Default Calendar"
|
|
17
|
+
resource = "europe.Germany"
|
|
18
|
+
timezone = zoneinfo.ZoneInfo("UTC")
|
|
19
|
+
|
|
20
|
+
class Meta:
|
|
21
|
+
model = DayOffCalendar
|
|
22
|
+
django_get_or_create = ["resource"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DayOffCalendarFactory(BaseDayOffCalendarFactory):
|
|
26
|
+
@factory.post_generation
|
|
27
|
+
def post_gen(self, create, extracted, **kwargs):
|
|
28
|
+
if create:
|
|
29
|
+
DefaultDailyPeriodFactory.create(calendar=self)
|
|
30
|
+
DefaultDailyPeriodFactory.create(
|
|
31
|
+
calendar=self,
|
|
32
|
+
lower_time=time(
|
|
33
|
+
14,
|
|
34
|
+
0,
|
|
35
|
+
0,
|
|
36
|
+
),
|
|
37
|
+
upper_time=time(18, 0, 0),
|
|
38
|
+
title="afternoon",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DefaultDailyPeriodFactory(factory.django.DjangoModelFactory):
|
|
43
|
+
lower_time = time(9, 0, 0)
|
|
44
|
+
upper_time = time(13, 0, 0)
|
|
45
|
+
title = "morning"
|
|
46
|
+
calendar = factory.SubFactory("wbhuman_resources.factories.BaseDayOffCalendarFactory")
|
|
47
|
+
|
|
48
|
+
class Meta:
|
|
49
|
+
model = DefaultDailyPeriod
|
|
50
|
+
django_get_or_create = ["lower_time", "upper_time", "calendar"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DayOffFactory(factory.django.DjangoModelFactory):
|
|
54
|
+
class Meta:
|
|
55
|
+
model = DayOff
|
|
56
|
+
|
|
57
|
+
title = factory.Faker("text", max_nb_chars=64)
|
|
58
|
+
date = factory.Faker("date_object")
|
|
59
|
+
calendar = factory.SubFactory("wbhuman_resources.factories.DayOffCalendarFactory")
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from django.contrib.auth.models import Group
|
|
3
|
+
from django.db.models.signals import post_save
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from wbhuman_resources.models import (
|
|
6
|
+
BalanceHourlyAllowance,
|
|
7
|
+
EmployeeHumanResource,
|
|
8
|
+
EmployeeWeeklyOffPeriods,
|
|
9
|
+
EmployeeYearBalance,
|
|
10
|
+
Position,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
fake = Faker()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PositionFactory(factory.django.DjangoModelFactory):
|
|
17
|
+
class Meta:
|
|
18
|
+
model = Position
|
|
19
|
+
|
|
20
|
+
name = factory.Faker("text", max_nb_chars=64)
|
|
21
|
+
manager = None
|
|
22
|
+
|
|
23
|
+
@factory.post_generation
|
|
24
|
+
def post_gen(self, create, extracted, **kwargs):
|
|
25
|
+
group = Group.objects.get_or_create(name="Test Group")[0]
|
|
26
|
+
self.groups.add(group)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@factory.django.mute_signals(post_save)
|
|
30
|
+
class EmployeeHumanResourceFactory(factory.django.DjangoModelFactory):
|
|
31
|
+
class Meta:
|
|
32
|
+
model = EmployeeHumanResource
|
|
33
|
+
django_get_or_create = ["profile"]
|
|
34
|
+
|
|
35
|
+
is_active = True
|
|
36
|
+
profile = factory.SubFactory("wbcore.contrib.authentication.factories.AuthenticatedPersonFactory")
|
|
37
|
+
direct_manager = factory.SubFactory("wbcore.contrib.directory.factories.PersonFactory")
|
|
38
|
+
calendar = factory.SubFactory("wbhuman_resources.factories.DayOffCalendarFactory")
|
|
39
|
+
position = factory.SubFactory(PositionFactory)
|
|
40
|
+
enrollment_at = factory.Faker("past_date")
|
|
41
|
+
extra_days_frequency = EmployeeHumanResource.ExtraDaysBalanceFrequency.YEARLY
|
|
42
|
+
|
|
43
|
+
contract_type = EmployeeHumanResource.ContractType.INTERNAL
|
|
44
|
+
occupancy_rate = factory.Faker("pyfloat", min_value=0, max_value=1)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class EmployeeYearBalanceFactory(factory.django.DjangoModelFactory):
|
|
48
|
+
class Meta:
|
|
49
|
+
model = EmployeeYearBalance
|
|
50
|
+
django_get_or_create = ["employee", "year"]
|
|
51
|
+
|
|
52
|
+
employee = factory.SubFactory("wbhuman_resources.factories.EmployeeHumanResourceFactory")
|
|
53
|
+
year = factory.Faker("pyint", min_value=1970, max_value=2022)
|
|
54
|
+
extra_balance = factory.Faker("pyint", min_value=0, max_value=20)
|
|
55
|
+
|
|
56
|
+
@factory.post_generation
|
|
57
|
+
def post_gen(self, create, extracted, **kwargs):
|
|
58
|
+
if create:
|
|
59
|
+
BalanceHourlyAllowanceFactory.create(balance=self)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BalanceHourlyAllowanceFactory(factory.django.DjangoModelFactory):
|
|
63
|
+
balance = factory.SubFactory("wbhuman_resources.factories.EmployeeYearBalanceFactory")
|
|
64
|
+
period_index = 1
|
|
65
|
+
hourly_allowance = factory.Faker("pyint", min_value=160, max_value=200)
|
|
66
|
+
|
|
67
|
+
class Meta:
|
|
68
|
+
model = BalanceHourlyAllowance
|
|
69
|
+
django_get_or_create = ["balance", "period_index"]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class EmployeeWeeklyOffPeriodsFactory(factory.django.DjangoModelFactory):
|
|
73
|
+
employee = factory.SubFactory("wbhuman_resources.factories.EmployeeHumanResourceFactory")
|
|
74
|
+
period = factory.LazyAttribute(lambda x: x.employee.calendar.default_periods.order_by("?")[0])
|
|
75
|
+
weekday = factory.Faker("pyint", min_value=0, max_value=7)
|
|
76
|
+
|
|
77
|
+
class Meta:
|
|
78
|
+
model = EmployeeWeeklyOffPeriods
|
|
79
|
+
django_get_or_create = ["employee", "period", "weekday"]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
|
|
4
|
+
import factory
|
|
5
|
+
import pytz
|
|
6
|
+
from django.utils import timezone
|
|
7
|
+
from faker import Faker
|
|
8
|
+
from psycopg.types.range import DateRange
|
|
9
|
+
from wbcore.contrib.authentication.factories import InternalUserFactory
|
|
10
|
+
from wbcore.contrib.directory.factories.entries import PersonFactory
|
|
11
|
+
from wbhuman_resources.models import (
|
|
12
|
+
KPI,
|
|
13
|
+
Evaluation,
|
|
14
|
+
Review,
|
|
15
|
+
ReviewAnswer,
|
|
16
|
+
ReviewGroup,
|
|
17
|
+
ReviewQuestion,
|
|
18
|
+
ReviewQuestionCategory,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
fake = Faker()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ReviewGroupFactory(factory.django.DjangoModelFactory):
|
|
25
|
+
class Meta:
|
|
26
|
+
model = ReviewGroup
|
|
27
|
+
|
|
28
|
+
name = factory.Faker("text", max_nb_chars=64)
|
|
29
|
+
|
|
30
|
+
@factory.post_generation
|
|
31
|
+
def employees(self, create, extracted, **kwargs):
|
|
32
|
+
if not create:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if extracted:
|
|
36
|
+
for employee in extracted:
|
|
37
|
+
self.employees.add(employee)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ReviewAbstractFactory(factory.django.DjangoModelFactory):
|
|
41
|
+
class Meta:
|
|
42
|
+
model = Review
|
|
43
|
+
django_get_or_create = ["moderator"]
|
|
44
|
+
|
|
45
|
+
review_group = factory.SubFactory(ReviewGroupFactory)
|
|
46
|
+
review = factory.Faker("date_time_between", start_date="+5d", end_date="+6d", tzinfo=pytz.utc)
|
|
47
|
+
moderator = factory.LazyAttribute(lambda o: InternalUserFactory.create().profile)
|
|
48
|
+
year = factory.Faker("pyint", min_value=1000, max_value=9999)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ReviewFactory(ReviewAbstractFactory):
|
|
52
|
+
class Meta:
|
|
53
|
+
model = Review
|
|
54
|
+
django_get_or_create = ["moderator", "reviewee", "reviewer"]
|
|
55
|
+
|
|
56
|
+
from_date = factory.Faker("date_between", start_date="+2d", end_date="+3d")
|
|
57
|
+
to_date = factory.Faker("date_between", start_date="+4d", end_date="+5d")
|
|
58
|
+
review_deadline = factory.Faker("date_between", start_date="+5d", end_date="+6d")
|
|
59
|
+
reviewee = factory.LazyAttribute(lambda o: InternalUserFactory.create().profile)
|
|
60
|
+
reviewer = factory.LazyAttribute(lambda o: InternalUserFactory.create().profile)
|
|
61
|
+
feedback_reviewee = factory.Faker("text")
|
|
62
|
+
feedback_reviewer = factory.Faker("text")
|
|
63
|
+
|
|
64
|
+
signed_reviewee = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
65
|
+
signed_reviewer = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
66
|
+
completely_filled_reviewee = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
67
|
+
completely_filled_reviewer = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class CompletedFilledReviewFactory(ReviewFactory):
|
|
71
|
+
completely_filled_reviewee = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
72
|
+
completely_filled_reviewer = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SignedReviewFactory(CompletedFilledReviewFactory):
|
|
76
|
+
signed_reviewee = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
77
|
+
signed_reviewer = factory.Faker("date_time", tzinfo=pytz.utc)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ReviewTemplateFactory(ReviewAbstractFactory):
|
|
81
|
+
is_template = True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ReviewQuestionCategoryFactory(factory.django.DjangoModelFactory):
|
|
85
|
+
class Meta:
|
|
86
|
+
model = ReviewQuestionCategory
|
|
87
|
+
|
|
88
|
+
name = factory.Faker("text", max_nb_chars=64)
|
|
89
|
+
order = factory.Faker("pyint", min_value=0, max_value=9999)
|
|
90
|
+
weight = factory.Faker("pydecimal", right_digits=1, min_value=0, max_value=9999)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ReviewQuestionNoCategoryFactory(factory.django.DjangoModelFactory):
|
|
94
|
+
class Meta:
|
|
95
|
+
model = ReviewQuestion
|
|
96
|
+
|
|
97
|
+
review = factory.SubFactory(ReviewFactory)
|
|
98
|
+
question = factory.Faker("text")
|
|
99
|
+
order = factory.Faker("pyint", min_value=0, max_value=9999)
|
|
100
|
+
weight = factory.Faker("pydecimal", right_digits=1, min_value=0, max_value=9999)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ReviewQuestionFactory(ReviewQuestionNoCategoryFactory):
|
|
104
|
+
category = factory.SubFactory(ReviewQuestionCategoryFactory)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ReviewAnswerFactory(factory.django.DjangoModelFactory):
|
|
108
|
+
class Meta:
|
|
109
|
+
model = ReviewAnswer
|
|
110
|
+
|
|
111
|
+
question = factory.SubFactory(ReviewQuestionFactory)
|
|
112
|
+
answered_by = factory.LazyAttribute(lambda o: InternalUserFactory.create().profile)
|
|
113
|
+
answered_anonymized = hash(factory.SelfAttribute("answered_by"))
|
|
114
|
+
answer_number = factory.Faker("pyint", min_value=0, max_value=9999)
|
|
115
|
+
answer_text = factory.Faker("text")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ReviewAnswerNoCategoryFactory(ReviewAnswerFactory):
|
|
119
|
+
question = factory.SubFactory(ReviewQuestionNoCategoryFactory)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class KPIFactory(factory.django.DjangoModelFactory):
|
|
123
|
+
class Meta:
|
|
124
|
+
model = KPI
|
|
125
|
+
|
|
126
|
+
name = factory.Faker("pystr")
|
|
127
|
+
goal = factory.Faker("pyint", min_value=0, max_value=9999)
|
|
128
|
+
period = DateRange(timezone.now().date(), timezone.now().date() + timedelta(days=random.randint(4, 5)))
|
|
129
|
+
handler = "wbcrm.kpi_handlers.activities.NumberOfActivityKPI"
|
|
130
|
+
|
|
131
|
+
@factory.post_generation
|
|
132
|
+
def evaluated_persons(self, create, extracted, **kwargs):
|
|
133
|
+
if not create:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if extracted:
|
|
137
|
+
for person in extracted:
|
|
138
|
+
self.evaluated_persons.add(person)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class DefaultPersonKPIFactory(KPIFactory):
|
|
142
|
+
@factory.post_generation
|
|
143
|
+
def evaluated_persons(self, create, extracted, **kwargs):
|
|
144
|
+
self.evaluated_persons.add(PersonFactory())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class EvaluationFactory(factory.django.DjangoModelFactory):
|
|
148
|
+
class Meta:
|
|
149
|
+
model = Evaluation
|
|
150
|
+
|
|
151
|
+
kpi = factory.SubFactory(KPIFactory)
|
|
152
|
+
person = factory.SubFactory("wbcore.contrib.directory.factories.PersonFactory")
|
|
153
|
+
evaluated_period = DateRange(timezone.now().date(), timezone.now().date() + timedelta(days=random.randint(4, 5)))
|
|
154
|
+
evaluation_date = timezone.now().date()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .absence import (
|
|
2
|
+
AbsenceRequestEmployeeHumanResourceFilterSet,
|
|
3
|
+
AbsenceRequestFilter,
|
|
4
|
+
AbsenceTypeCountEmployeeModelFilterSet,
|
|
5
|
+
)
|
|
6
|
+
from .absence_graphs import AbsenceRequestPlannerFilter, AbsenceTableFilter
|
|
7
|
+
from .calendars import DayOffFilter
|
|
8
|
+
from .employee import EmployeeBalanceFilterSet, EmployeeFilterSet, PositionFilterSet
|
|
9
|
+
from .kpi import KPIFilterSet, KPIEvaluationFilterSet, KPIEvaluationPandasFilter
|
|
10
|
+
from .review import (
|
|
11
|
+
ReviewGroupFilter,
|
|
12
|
+
ReviewTemplateFilter,
|
|
13
|
+
ReviewFilter,
|
|
14
|
+
ReviewQuestionCategoryFilter,
|
|
15
|
+
ReviewQuestionFilter,
|
|
16
|
+
ReviewAnswerFilter,
|
|
17
|
+
ReviewProgressReviewFilter,
|
|
18
|
+
RatingReviewAnswerReviewFilter,
|
|
19
|
+
)
|
|
20
|
+
from .signals import *
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from django.utils.timezone import localdate
|
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
|
6
|
+
from psycopg.types.range import TimestamptzRange
|
|
7
|
+
from wbcore import filters as wb_filters
|
|
8
|
+
from wbcore.contrib.agenda.filters import CalendarItemPeriodBaseFilterSet
|
|
9
|
+
from wbhuman_resources.models import (
|
|
10
|
+
AbsenceRequest,
|
|
11
|
+
AbsenceRequestPeriods,
|
|
12
|
+
AbsenceRequestType,
|
|
13
|
+
Position,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def current_year_date_start(*args, **kwargs):
|
|
18
|
+
d = localdate()
|
|
19
|
+
return date(d.year, 1, 1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def current_year_date_end(*args, **kwargs):
|
|
23
|
+
d = localdate()
|
|
24
|
+
return max((d + pd.tseries.offsets.YearEnd(1)).date(), d + timedelta(days=60))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AbsenceRequestFilter(CalendarItemPeriodBaseFilterSet):
|
|
28
|
+
conference_room = boolean_conference_room = None
|
|
29
|
+
department = wb_filters.ModelChoiceFilter(
|
|
30
|
+
label=_("Department"),
|
|
31
|
+
queryset=Position.objects.all(),
|
|
32
|
+
endpoint=Position.get_representation_endpoint(),
|
|
33
|
+
value_key=Position.get_representation_value_key(),
|
|
34
|
+
label_key=Position.get_representation_label_key(),
|
|
35
|
+
method="filter_position",
|
|
36
|
+
)
|
|
37
|
+
is_active_employee = wb_filters.BooleanFilter(
|
|
38
|
+
label=_("Is Employee Active"), method="boolean_is_active_employee", default=True
|
|
39
|
+
)
|
|
40
|
+
_total_hours_in_days__gte = wb_filters.NumberFilter(
|
|
41
|
+
lookup_expr="gte", label="Total hours", field_name="_total_hours_in_days"
|
|
42
|
+
)
|
|
43
|
+
_total_vacation_hours_in_days__gte = wb_filters.NumberFilter(
|
|
44
|
+
lookup_expr="gte", label="Total hours", field_name="_total_vacation_hours_in_days"
|
|
45
|
+
)
|
|
46
|
+
_total_hours_in_days__lte = wb_filters.NumberFilter(
|
|
47
|
+
lookup_expr="lte", label="Total hours", field_name="_total_hours_in_days"
|
|
48
|
+
)
|
|
49
|
+
_total_vacation_hours_in_days__lte = wb_filters.NumberFilter(
|
|
50
|
+
lookup_expr="lte", label="Total hours", field_name="_total_vacation_hours_in_days"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def get_default_period(self):
|
|
54
|
+
return TimestamptzRange(lower=current_year_date_start(), upper=current_year_date_end())
|
|
55
|
+
|
|
56
|
+
def boolean_is_active_employee(self, queryset, name, value):
|
|
57
|
+
if value:
|
|
58
|
+
return queryset.filter(employee__is_active=True)
|
|
59
|
+
# return queryset.filter(employee__in=EmployeeHumanResource.active_internal_employees.all())
|
|
60
|
+
return queryset
|
|
61
|
+
|
|
62
|
+
def filter_position(self, queryset, name, value):
|
|
63
|
+
if value:
|
|
64
|
+
return queryset.filter(department__in=value.get_descendants(include_self=True)).distinct()
|
|
65
|
+
return queryset
|
|
66
|
+
|
|
67
|
+
class Meta:
|
|
68
|
+
model = AbsenceRequest
|
|
69
|
+
fields = {
|
|
70
|
+
"employee": ["exact"],
|
|
71
|
+
"status": ["exact"],
|
|
72
|
+
"type": ["exact"],
|
|
73
|
+
"created": ["gte", "exact", "lte"],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AbsenceTypeCountEmployeeModelFilterSet(wb_filters.FilterSet):
|
|
78
|
+
year = wb_filters.YearFilter(field_name="year", lookup_expr="exact")
|
|
79
|
+
|
|
80
|
+
absence_type = wb_filters.ModelChoiceFilter(
|
|
81
|
+
label=_("Type"),
|
|
82
|
+
queryset=AbsenceRequestType.objects.all(),
|
|
83
|
+
endpoint=AbsenceRequestType.get_representation_endpoint(),
|
|
84
|
+
value_key=AbsenceRequestType.get_representation_value_key(),
|
|
85
|
+
label_key=AbsenceRequestType.get_representation_label_key(),
|
|
86
|
+
method="filter_absence_type",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def filter_absence_type(self, queryset, label, value):
|
|
90
|
+
if value:
|
|
91
|
+
return queryset.filter(request__type=value)
|
|
92
|
+
return queryset
|
|
93
|
+
|
|
94
|
+
class Meta:
|
|
95
|
+
model = AbsenceRequestPeriods
|
|
96
|
+
fields = {}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AbsenceRequestEmployeeHumanResourceFilterSet(AbsenceRequestFilter):
|
|
100
|
+
department = is_active_employee = None
|
|
101
|
+
|
|
102
|
+
class Meta:
|
|
103
|
+
model = AbsenceRequest
|
|
104
|
+
fields = {
|
|
105
|
+
"status": ["exact"],
|
|
106
|
+
"type": ["exact"],
|
|
107
|
+
"created": ["gte", "exact", "lte"],
|
|
108
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from django.utils.timezone import localdate
|
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
|
6
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
7
|
+
from psycopg.types.range import TimestamptzRange
|
|
8
|
+
from wbcore import filters as wb_filters
|
|
9
|
+
from wbhuman_resources.models import AbsenceRequestPeriods, DayOffCalendar, Position
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def current_year_date_range(*args, **kwargs):
|
|
13
|
+
d = localdate()
|
|
14
|
+
return TimestamptzRange(
|
|
15
|
+
(localdate() - pd.tseries.offsets.Week(weekday=0)).date(),
|
|
16
|
+
max((d + pd.tseries.offsets.YearEnd(1)).date(), d + timedelta(days=60)),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def monday_of_current_week(*args, **kwargs):
|
|
21
|
+
today = localdate()
|
|
22
|
+
return today - timedelta(days=today.weekday())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_calendar_default(field, request, view, **kwargs) -> int | None:
|
|
26
|
+
if (profile := request.user.profile) and (employee := getattr(profile, "human_resources", None)):
|
|
27
|
+
return employee.calendar.id
|
|
28
|
+
if calendar := global_preferences_registry.manager()["wbhuman_resources__employee_default_calendar"]:
|
|
29
|
+
return calendar.id
|
|
30
|
+
try:
|
|
31
|
+
return DayOffCalendar.objects.first().id
|
|
32
|
+
except AttributeError:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AbsenceRequestPlannerFilter(wb_filters.FilterSet):
|
|
37
|
+
calendar = wb_filters.ModelChoiceFilter(
|
|
38
|
+
label=_("Calendar"),
|
|
39
|
+
required=True,
|
|
40
|
+
clearable=False,
|
|
41
|
+
queryset=DayOffCalendar.objects.all(),
|
|
42
|
+
endpoint=DayOffCalendar.get_representation_endpoint(),
|
|
43
|
+
value_key=DayOffCalendar.get_representation_value_key(),
|
|
44
|
+
label_key=DayOffCalendar.get_representation_label_key(),
|
|
45
|
+
default=get_calendar_default,
|
|
46
|
+
method=lambda queryset, label, value: queryset,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
date = wb_filters.DateRangeFilter(
|
|
50
|
+
label=_("Date Range"),
|
|
51
|
+
method=lambda queryset, label, value: queryset,
|
|
52
|
+
required=True,
|
|
53
|
+
clearable=False,
|
|
54
|
+
default=current_year_date_range,
|
|
55
|
+
)
|
|
56
|
+
only_employee_with_absence_periods = wb_filters.BooleanFilter(
|
|
57
|
+
default=False,
|
|
58
|
+
label=_("Only Employee With Absence periods"),
|
|
59
|
+
method=lambda queryset, label, value: queryset,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
position = wb_filters.ModelChoiceFilter(
|
|
63
|
+
label=_("Position"),
|
|
64
|
+
queryset=Position.objects.all(),
|
|
65
|
+
endpoint=Position.get_representation_endpoint(),
|
|
66
|
+
value_key=Position.get_representation_value_key(),
|
|
67
|
+
label_key=Position.get_representation_label_key(),
|
|
68
|
+
method=lambda queryset, label, value: queryset,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
class Meta:
|
|
72
|
+
model = AbsenceRequestPeriods
|
|
73
|
+
fields = {}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AbsenceTableFilter(AbsenceRequestPlannerFilter):
|
|
77
|
+
date_gte = date_lte = None
|
|
78
|
+
date = wb_filters.DateFilter(
|
|
79
|
+
label=_("Week Day"),
|
|
80
|
+
method=lambda queryset, label, value: queryset,
|
|
81
|
+
default=monday_of_current_week,
|
|
82
|
+
required=True,
|
|
83
|
+
help_text="Change this date to any day on the week you are interested in seeing the presence table",
|
|
84
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from wbcore import filters as wb_filters
|
|
3
|
+
from wbcore.filters.defaults import current_year_date_range
|
|
4
|
+
from wbhuman_resources.models import DayOff
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DayOffFilter(wb_filters.FilterSet):
|
|
8
|
+
date = wb_filters.DateRangeFilter(
|
|
9
|
+
label=_("Date Range"),
|
|
10
|
+
required=True,
|
|
11
|
+
clearable=False,
|
|
12
|
+
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
13
|
+
default=current_year_date_range,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def start_filter(self, queryset, name, value):
|
|
17
|
+
if value:
|
|
18
|
+
return queryset.filter(date__gte=value)
|
|
19
|
+
return queryset
|
|
20
|
+
|
|
21
|
+
def end_filter(self, queryset, name, value):
|
|
22
|
+
if value:
|
|
23
|
+
return queryset.filter(date__lte=value)
|
|
24
|
+
return queryset
|
|
25
|
+
|
|
26
|
+
class Meta:
|
|
27
|
+
model = DayOff
|
|
28
|
+
fields = {"count_as_holiday": ["exact"], "calendar": ["exact"]}
|