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,199 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Iterator, Protocol, Type
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.contrib.postgres.fields import DateRangeField
|
|
9
|
+
from django.db import models
|
|
10
|
+
from django.db.models.query import QuerySet
|
|
11
|
+
from django.utils.translation import gettext_lazy as _
|
|
12
|
+
from wbcore.models import WBModel
|
|
13
|
+
from wbcore.serializers.serializers import ModelSerializer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class KPIHandler(Protocol):
|
|
17
|
+
"""
|
|
18
|
+
This is a protocol to ensure that two methods are present in the implemented handlers.
|
|
19
|
+
A handler makes sure that we have a valid serializer and a valid evaluate method to customize
|
|
20
|
+
KPIs.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def get_name(self) -> str: ...
|
|
24
|
+
|
|
25
|
+
def get_serializer(self) -> Type[ModelSerializer]: ...
|
|
26
|
+
|
|
27
|
+
def annotate_parameters(self, queryset: QuerySet["KPI"]) -> QuerySet["KPI"]: ...
|
|
28
|
+
|
|
29
|
+
def evaluate(self, kpi: "KPI") -> int: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def default_additional_data() -> dict[str, dict | list]:
|
|
33
|
+
return {
|
|
34
|
+
"serializer_data": {},
|
|
35
|
+
"list_data": [],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class KPI(WBModel):
|
|
40
|
+
"""
|
|
41
|
+
Key Performance Indicator. Stores 1 KPI for either a person or a group of people. It defines a goal and a method to evaluate this goal.
|
|
42
|
+
Additional parameters are set through a custom serializer which stores its result in a JSON field. The visualizion of it is done by
|
|
43
|
+
passing the json field in the custom serializer. The addition parameters in the json field need to have to keys, one for the raw data that
|
|
44
|
+
is passed into the form serializer and one for a list to show the parameters in human readable form in a list field.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
class Interval(models.TextChoices):
|
|
48
|
+
DAILY = "DAILY", _("Daily")
|
|
49
|
+
WEEKLY = "WEEKLY", _("Weekly")
|
|
50
|
+
MONTHLY = "MONTHLY", _("Monthly")
|
|
51
|
+
QUARTERLY = "QUARTERLY", _("Quarterly")
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def get_frequence_correspondance(cls, name):
|
|
55
|
+
_map = {
|
|
56
|
+
"DAILY": "D",
|
|
57
|
+
"WEEKLY": "W",
|
|
58
|
+
"MONTHLY": "ME",
|
|
59
|
+
"QUARTERLY": "3M",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return _map[name]
|
|
63
|
+
|
|
64
|
+
name = models.CharField(max_length=255)
|
|
65
|
+
goal = models.PositiveIntegerField(verbose_name=_("Goal"))
|
|
66
|
+
period = DateRangeField(verbose_name=_("Period"))
|
|
67
|
+
|
|
68
|
+
evaluated_persons = models.ManyToManyField(
|
|
69
|
+
to="directory.Person", related_name="wbhuman_resources_kpis", verbose_name=_("Evaluated Persons")
|
|
70
|
+
)
|
|
71
|
+
evaluated_intervals = models.CharField(
|
|
72
|
+
max_length=16, choices=Interval.choices, default=Interval.MONTHLY, verbose_name=_("Evaluated Intervals")
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
handler = models.CharField(max_length=255)
|
|
76
|
+
additional_data = models.JSONField(default=default_additional_data)
|
|
77
|
+
last_update = models.DateTimeField(
|
|
78
|
+
auto_now=True, verbose_name=_("Last update"), help_text=_("Date of latest change (automatically computed)")
|
|
79
|
+
)
|
|
80
|
+
individual_evaluation = models.BooleanField(default=True, verbose_name=_("Individual Evaluation"))
|
|
81
|
+
is_active = models.BooleanField(default=True, verbose_name=_("Is Active"))
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def get_all_handlers() -> Iterator[KPIHandler]:
|
|
85
|
+
for param in settings.KPI_HANDLERS:
|
|
86
|
+
handler_path = param[0]
|
|
87
|
+
*module, handler = handler_path.split(".")
|
|
88
|
+
module_path = ".".join(module)
|
|
89
|
+
yield getattr(importlib.import_module(module_path), handler)()
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def get_all_handler_choices() -> Iterator[tuple[str, str]]:
|
|
93
|
+
yield from getattr(settings, "KPI_HANDLERS", [])
|
|
94
|
+
|
|
95
|
+
def get_handler(self) -> KPIHandler:
|
|
96
|
+
*module, handler = self.handler.split(".")
|
|
97
|
+
module_path = ".".join(module)
|
|
98
|
+
return getattr(importlib.import_module(module_path), handler)()
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def is_administrator(cls, user):
|
|
102
|
+
user_groups = user.groups
|
|
103
|
+
user_permission = user.user_permissions
|
|
104
|
+
return (
|
|
105
|
+
user_groups.filter(permissions__codename="administrate_kpi").exists()
|
|
106
|
+
or user_permission.filter(codename="administrate_kpi").exists()
|
|
107
|
+
or user.is_superuser
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def generate_evaluation(self, evaluation_date: date):
|
|
111
|
+
if self.individual_evaluation:
|
|
112
|
+
persons = self.evaluated_persons.all()
|
|
113
|
+
else:
|
|
114
|
+
persons = [None]
|
|
115
|
+
|
|
116
|
+
for person in persons:
|
|
117
|
+
if evaluation_date <= date.today() and evaluation_date > self.period.lower:
|
|
118
|
+
Evaluation.objects.update_or_create(
|
|
119
|
+
evaluation_date=evaluation_date,
|
|
120
|
+
person=person,
|
|
121
|
+
kpi=self,
|
|
122
|
+
defaults={
|
|
123
|
+
"evaluated_period": [self.period.lower, evaluation_date],
|
|
124
|
+
"evaluated_score": self.get_handler().evaluate(
|
|
125
|
+
self, evaluated_person=person, evaluation_date=evaluation_date
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
class Meta:
|
|
131
|
+
verbose_name = _("Key Performance Indicator")
|
|
132
|
+
verbose_name_plural = _("Key Performance Indicators")
|
|
133
|
+
permissions = [("administrate_kpi", "Can Administrate KPI")]
|
|
134
|
+
|
|
135
|
+
def __str__(self):
|
|
136
|
+
return f"{self.name}"
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def get_representation_endpoint(cls):
|
|
140
|
+
return "wbhuman_resources:kpi-list"
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def get_representation_value_key(cls):
|
|
144
|
+
return "id"
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def get_representation_label_key(cls):
|
|
148
|
+
return "{{name}}"
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def get_endpoint_basename(cls):
|
|
152
|
+
return "wbhuman_resources:kpi"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class Evaluation(models.Model):
|
|
156
|
+
"""
|
|
157
|
+
Stores the evaluated result of a KPI on the evaluation day for the previous period for 1 person.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
kpi = models.ForeignKey(
|
|
161
|
+
to="wbhuman_resources.KPI", related_name="evaluations", on_delete=models.CASCADE, verbose_name=_("KPI")
|
|
162
|
+
)
|
|
163
|
+
person = models.ForeignKey(
|
|
164
|
+
to="directory.Person",
|
|
165
|
+
related_name="wbhuman_resources_evaluations",
|
|
166
|
+
on_delete=models.CASCADE,
|
|
167
|
+
blank=True,
|
|
168
|
+
null=True,
|
|
169
|
+
)
|
|
170
|
+
evaluated_score = models.IntegerField(null=True, blank=True)
|
|
171
|
+
evaluated_period = DateRangeField()
|
|
172
|
+
evaluation_date = models.DateField()
|
|
173
|
+
last_update = models.DateTimeField(
|
|
174
|
+
auto_now=True, verbose_name=_("Last update"), help_text=_("Date of latest change (automatically computed)")
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
class Meta:
|
|
178
|
+
verbose_name = _("Evaluation")
|
|
179
|
+
verbose_name_plural = _("Evaluations")
|
|
180
|
+
|
|
181
|
+
def __str__(self) -> str:
|
|
182
|
+
return f"{self.kpi} - {self.evaluation_date}"
|
|
183
|
+
|
|
184
|
+
def get_rating(self):
|
|
185
|
+
list_date = list(
|
|
186
|
+
pd.date_range(
|
|
187
|
+
start=self.kpi.period.lower,
|
|
188
|
+
end=self.kpi.period.upper,
|
|
189
|
+
freq=KPI.Interval.get_frequence_correspondance(self.kpi.evaluated_intervals),
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
list_date = [_date.date() for _date in list_date]
|
|
193
|
+
index = list_date.index(self.evaluation_date)
|
|
194
|
+
nb_intervals = len(list_date)
|
|
195
|
+
goals_expected = (self.kpi.goal * np.ones(nb_intervals) / nb_intervals).cumsum().round(2)
|
|
196
|
+
goal_expected = goals_expected[index]
|
|
197
|
+
rating = (self.evaluated_score / goal_expected * 100).round(2)
|
|
198
|
+
rating = round(rating / 100 * 4)
|
|
199
|
+
return 1 if rating < 1 else rating if rating < 4 else 4
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from django.db.utils import ProgrammingError
|
|
4
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
5
|
+
from pandas.tseries.offsets import MonthBegin
|
|
6
|
+
|
|
7
|
+
from wbhuman_resources.dynamic_preferences_registry import (
|
|
8
|
+
NumberOfMonthsBeforeBalanceExpiration,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def default_vacation_days_per_year():
|
|
13
|
+
return global_preferences_registry.manager()["wbhuman_resources__default_vacation_days"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def long_vacation_number_of_days():
|
|
17
|
+
return global_preferences_registry.manager()["wbhuman_resources__long_vacation_number_of_days"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_main_company():
|
|
21
|
+
return global_preferences_registry.manager()["directory__main_company"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_previous_year_balance_expiration_date(year: int) -> date:
|
|
25
|
+
next_year_date = date(year + 1, 1, 1)
|
|
26
|
+
try:
|
|
27
|
+
number_of_month_before_balance_expiration = global_preferences_registry.manager()[
|
|
28
|
+
"wbhuman_resources__number_of_month_before_balance_expiration"
|
|
29
|
+
]
|
|
30
|
+
except (RuntimeError, ProgrammingError):
|
|
31
|
+
number_of_month_before_balance_expiration = NumberOfMonthsBeforeBalanceExpiration.default
|
|
32
|
+
|
|
33
|
+
return (next_year_date + MonthBegin(number_of_month_before_balance_expiration)).date()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_is_external_considered_as_internal():
|
|
37
|
+
try:
|
|
38
|
+
return global_preferences_registry.manager()["wbhuman_resources__is_external_considered_as_internal"]
|
|
39
|
+
except (RuntimeError, ProgrammingError):
|
|
40
|
+
return False
|