wbhuman_resources 2.2.1__tar.gz
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-2.2.1/.gitignore +181 -0
- wbhuman_resources-2.2.1/PKG-INFO +9 -0
- wbhuman_resources-2.2.1/pyproject.toml +32 -0
- wbhuman_resources-2.2.1/wbhuman_resources/__init__.py +1 -0
- wbhuman_resources-2.2.1/wbhuman_resources/admin/__init__.py +5 -0
- wbhuman_resources-2.2.1/wbhuman_resources/admin/absence.py +113 -0
- wbhuman_resources-2.2.1/wbhuman_resources/admin/calendars.py +37 -0
- wbhuman_resources-2.2.1/wbhuman_resources/admin/employee.py +109 -0
- wbhuman_resources-2.2.1/wbhuman_resources/admin/kpi.py +21 -0
- wbhuman_resources-2.2.1/wbhuman_resources/admin/review.py +157 -0
- wbhuman_resources-2.2.1/wbhuman_resources/apps.py +22 -0
- wbhuman_resources-2.2.1/wbhuman_resources/dynamic_preferences_registry.py +118 -0
- wbhuman_resources-2.2.1/wbhuman_resources/factories/__init__.py +38 -0
- wbhuman_resources-2.2.1/wbhuman_resources/factories/absence.py +108 -0
- wbhuman_resources-2.2.1/wbhuman_resources/factories/calendars.py +59 -0
- wbhuman_resources-2.2.1/wbhuman_resources/factories/employee.py +79 -0
- wbhuman_resources-2.2.1/wbhuman_resources/factories/kpi.py +154 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/__init__.py +20 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/absence.py +108 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/absence_graphs.py +84 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/calendars.py +28 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/employee.py +80 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/kpi.py +34 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/review.py +133 -0
- wbhuman_resources-2.2.1/wbhuman_resources/filters/signals.py +26 -0
- wbhuman_resources-2.2.1/wbhuman_resources/locale/de/LC_MESSAGES/django.po +2456 -0
- wbhuman_resources-2.2.1/wbhuman_resources/management/__init__.py +23 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0001_initial_squashed_squashed_0015_alter_absencerequest_calendaritem_ptr_and_more.py +949 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0016_alter_employeehumanresource_options.py +20 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0017_absencerequest_crossborder_country_and_more.py +55 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0018_remove_position_group_position_groups.py +32 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0019_alter_absencerequest_options_alter_kpi_options_and_more.py +44 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0020_alter_employeeyearbalance_year_alter_review_year.py +27 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0021_alter_position_color.py +18 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/0022_remove_review_editable_mode.py +64 -0
- wbhuman_resources-2.2.1/wbhuman_resources/migrations/__init__.py +0 -0
- wbhuman_resources-2.2.1/wbhuman_resources/models/__init__.py +23 -0
- wbhuman_resources-2.2.1/wbhuman_resources/models/absence.py +889 -0
- wbhuman_resources-2.2.1/wbhuman_resources/models/calendars.py +370 -0
- wbhuman_resources-2.2.1/wbhuman_resources/models/employee.py +1211 -0
- wbhuman_resources-2.2.1/wbhuman_resources/models/kpi.py +199 -0
- wbhuman_resources-2.2.1/wbhuman_resources/models/preferences.py +39 -0
- wbhuman_resources-2.2.1/wbhuman_resources/models/review.py +978 -0
- wbhuman_resources-2.2.1/wbhuman_resources/permissions/__init__.py +0 -0
- wbhuman_resources-2.2.1/wbhuman_resources/permissions/backend.py +25 -0
- wbhuman_resources-2.2.1/wbhuman_resources/serializers/__init__.py +49 -0
- wbhuman_resources-2.2.1/wbhuman_resources/serializers/absence.py +296 -0
- wbhuman_resources-2.2.1/wbhuman_resources/serializers/calendars.py +71 -0
- wbhuman_resources-2.2.1/wbhuman_resources/serializers/employee.py +266 -0
- wbhuman_resources-2.2.1/wbhuman_resources/serializers/kpi.py +79 -0
- wbhuman_resources-2.2.1/wbhuman_resources/serializers/review.py +414 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tasks.py +189 -0
- wbhuman_resources-2.2.1/wbhuman_resources/templates/review/review_report.html +322 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/__init__.py +1 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/conftest.py +96 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/models/__init__.py +0 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/models/test_absences.py +477 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/models/test_calendars.py +208 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/models/test_employees.py +501 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/models/test_review.py +102 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/models/test_utils.py +109 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/signals.py +107 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/test_permission.py +63 -0
- wbhuman_resources-2.2.1/wbhuman_resources/tests/test_tasks.py +73 -0
- wbhuman_resources-2.2.1/wbhuman_resources/urls.py +220 -0
- wbhuman_resources-2.2.1/wbhuman_resources/utils.py +40 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/__init__.py +61 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/absence.py +327 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/absence_charts.py +327 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/buttons/__init__.py +7 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/buttons/absence.py +31 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/buttons/employee.py +43 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/buttons/kpis.py +16 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/buttons/review.py +194 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/calendars.py +102 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/display/__init__.py +39 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/display/absence.py +344 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/display/calendars.py +84 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/display/employee.py +253 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/display/kpis.py +91 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/display/review.py +427 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/employee.py +209 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/endpoints/__init__.py +42 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/endpoints/absence.py +74 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/endpoints/calendars.py +18 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/endpoints/employee.py +57 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/endpoints/kpis.py +58 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/endpoints/review.py +203 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/kpi.py +279 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/menu/__init__.py +22 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/menu/absence.py +50 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/menu/administration.py +15 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/menu/calendars.py +33 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/menu/employee.py +44 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/menu/kpis.py +18 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/menu/review.py +97 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/mixins.py +13 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/review.py +836 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/titles/__init__.py +18 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/titles/absence.py +29 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/titles/employee.py +18 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/titles/kpis.py +14 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/titles/review.py +61 -0
- wbhuman_resources-2.2.1/wbhuman_resources/viewsets/utils.py +28 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
~
|
|
2
|
+
|
|
3
|
+
# Docker volumes
|
|
4
|
+
volumes/
|
|
5
|
+
# Poetry auth file
|
|
6
|
+
auth.toml
|
|
7
|
+
|
|
8
|
+
media/*
|
|
9
|
+
media/
|
|
10
|
+
mediafiles/
|
|
11
|
+
mediafiles/*
|
|
12
|
+
test/*
|
|
13
|
+
staticfiles/*
|
|
14
|
+
staticfiles/
|
|
15
|
+
#
|
|
16
|
+
# Byte-compiled / optimized / DLL files
|
|
17
|
+
__pycache__/
|
|
18
|
+
*.py[cod]
|
|
19
|
+
*$py.class
|
|
20
|
+
|
|
21
|
+
# C extensions
|
|
22
|
+
*.so
|
|
23
|
+
|
|
24
|
+
# Distribution / packaging
|
|
25
|
+
.Python
|
|
26
|
+
build/
|
|
27
|
+
develop-eggs/
|
|
28
|
+
dist/
|
|
29
|
+
info/
|
|
30
|
+
downloads/
|
|
31
|
+
eggs/
|
|
32
|
+
.eggs/
|
|
33
|
+
lib/
|
|
34
|
+
lib64/
|
|
35
|
+
parts/
|
|
36
|
+
sdist/
|
|
37
|
+
var/
|
|
38
|
+
wheels/
|
|
39
|
+
share/python-wheels/
|
|
40
|
+
*.egg-info/
|
|
41
|
+
.installed.cfg
|
|
42
|
+
*.egg
|
|
43
|
+
MANIFEST
|
|
44
|
+
|
|
45
|
+
# PyInstaller
|
|
46
|
+
# Usually these files are written by a python script from a template
|
|
47
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
48
|
+
*.manifest
|
|
49
|
+
*.spec
|
|
50
|
+
|
|
51
|
+
# Installer logs
|
|
52
|
+
pip-log.txt
|
|
53
|
+
pip-delete-this-directory.txt
|
|
54
|
+
|
|
55
|
+
# Unit test / coverage reports
|
|
56
|
+
htmlcov/
|
|
57
|
+
.tox/
|
|
58
|
+
.nox/
|
|
59
|
+
.coverage
|
|
60
|
+
.coverage.*
|
|
61
|
+
.cache
|
|
62
|
+
.dccache
|
|
63
|
+
nosetests.xml
|
|
64
|
+
coverage.xml
|
|
65
|
+
*.cover
|
|
66
|
+
*.py,cover
|
|
67
|
+
.hypothesis/
|
|
68
|
+
.pytest_cache/
|
|
69
|
+
cover/
|
|
70
|
+
report.xml
|
|
71
|
+
*/report.xml
|
|
72
|
+
|
|
73
|
+
# Translations
|
|
74
|
+
*.mo
|
|
75
|
+
*.pot
|
|
76
|
+
|
|
77
|
+
# Django stuff:
|
|
78
|
+
*.log
|
|
79
|
+
local_settings.py
|
|
80
|
+
*.sqlite3
|
|
81
|
+
db.sqlite3-journal
|
|
82
|
+
|
|
83
|
+
# Flask stuff:
|
|
84
|
+
instance/
|
|
85
|
+
.webassets-cache
|
|
86
|
+
|
|
87
|
+
# Scrapy stuff:
|
|
88
|
+
.scrapy
|
|
89
|
+
|
|
90
|
+
# Sphinx documentation
|
|
91
|
+
docs/_build/
|
|
92
|
+
|
|
93
|
+
# PyBuilder
|
|
94
|
+
.pybuilder/
|
|
95
|
+
target/
|
|
96
|
+
|
|
97
|
+
# Jupyter Notebook
|
|
98
|
+
.ipynb_checkpoints
|
|
99
|
+
*.ipynb
|
|
100
|
+
# IPython
|
|
101
|
+
profile_default/
|
|
102
|
+
ipython_config.py
|
|
103
|
+
|
|
104
|
+
# pyenv
|
|
105
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
106
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
107
|
+
# .python-version
|
|
108
|
+
|
|
109
|
+
# pipenv
|
|
110
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
111
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
112
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
113
|
+
# install all needed dependencies.
|
|
114
|
+
#Pipfile.lock
|
|
115
|
+
|
|
116
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
117
|
+
__pypackages__/
|
|
118
|
+
|
|
119
|
+
# Celery stuff
|
|
120
|
+
celerybeat-schedule
|
|
121
|
+
celerybeat.pid
|
|
122
|
+
|
|
123
|
+
# SageMath parsed files
|
|
124
|
+
*.sage.py
|
|
125
|
+
|
|
126
|
+
# Environments
|
|
127
|
+
.env
|
|
128
|
+
.envrc
|
|
129
|
+
.venv
|
|
130
|
+
env/
|
|
131
|
+
venv/
|
|
132
|
+
ENV/
|
|
133
|
+
env.bak/
|
|
134
|
+
venv.bak/
|
|
135
|
+
.vscode/
|
|
136
|
+
.idea/
|
|
137
|
+
.idea.bkp/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
crm/
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# Gitlab Runner
|
|
164
|
+
builds
|
|
165
|
+
builds/
|
|
166
|
+
|
|
167
|
+
# Integrator Office 365 : reverse proxy tunnel for outlook365
|
|
168
|
+
ngrok
|
|
169
|
+
*/ngrok
|
|
170
|
+
/modules/**/system/
|
|
171
|
+
|
|
172
|
+
/modules/wbmailing/files/*
|
|
173
|
+
/modules/wbmailing/mailing/*
|
|
174
|
+
|
|
175
|
+
/projects/*/requirements.txt
|
|
176
|
+
public
|
|
177
|
+
|
|
178
|
+
# Ignore archive localization generated folder
|
|
179
|
+
backend/modules/**/archive/*
|
|
180
|
+
**/**/requirements.txt
|
|
181
|
+
CHANGELOG-*
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: wbhuman_resources
|
|
3
|
+
Version: 2.2.1
|
|
4
|
+
Summary: A workbench module for managing human resources.
|
|
5
|
+
Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
|
|
6
|
+
Requires-Dist: wbcore
|
|
7
|
+
Requires-Dist: weasyprint==57.*
|
|
8
|
+
Requires-Dist: workalendar==17.*
|
|
9
|
+
Requires-Dist: xlsxwriter==3.*
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "wbhuman_resources"
|
|
3
|
+
description = "A workbench module for managing human resources."
|
|
4
|
+
authors = [{ name = "Christopher Wittlinger", email = "c.wittlinger@stainly.com"}]
|
|
5
|
+
dynamic = ["version"]
|
|
6
|
+
|
|
7
|
+
dependencies = [
|
|
8
|
+
"wbcore",
|
|
9
|
+
"weasyprint == 57.*",
|
|
10
|
+
"workalendar == 17.*",
|
|
11
|
+
"xlsxwriter==3.*",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[tool.uv.sources]
|
|
15
|
+
wbcore = { workspace = true }
|
|
16
|
+
|
|
17
|
+
[tool.uv]
|
|
18
|
+
package = true
|
|
19
|
+
|
|
20
|
+
[tool.hatch.version]
|
|
21
|
+
path = "../../pyproject.toml"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.sdist]
|
|
24
|
+
include = ["wbhuman_resources/*"]
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel]
|
|
27
|
+
packages = ["wbhuman_resources"]
|
|
28
|
+
only-packages = true
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["hatchling"]
|
|
32
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from django.contrib import admin
|
|
5
|
+
from django.shortcuts import redirect, render
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
from reversion.errors import RevertError
|
|
8
|
+
from wbcore.admin import CsvImportForm, ImportCsvMixin
|
|
9
|
+
|
|
10
|
+
from ..models.absence import AbsenceRequest, AbsenceRequestPeriods, AbsenceRequestType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CustomImportCsvMixin(ImportCsvMixin):
|
|
14
|
+
def _import_csv(self, request, _sep=";"):
|
|
15
|
+
if request.method == "POST":
|
|
16
|
+
csv_file = request.FILES["csv_file"]
|
|
17
|
+
|
|
18
|
+
str_text = ""
|
|
19
|
+
for line in csv_file:
|
|
20
|
+
str_text = str_text + line.decode()
|
|
21
|
+
# Import csv as df
|
|
22
|
+
df = pd.read_csv(StringIO(str_text), sep=_sep)
|
|
23
|
+
# Sanitize dataframe
|
|
24
|
+
df = df.where(pd.notnull(df), None)
|
|
25
|
+
df = df.drop(df.columns.difference(self.get_import_fields()), axis=1)
|
|
26
|
+
|
|
27
|
+
# Overide this function if there is foreign key ids in the dataframe
|
|
28
|
+
df = self.manipulate_df(df)
|
|
29
|
+
errors = 0
|
|
30
|
+
revert_errors = 0
|
|
31
|
+
nb_added = 0
|
|
32
|
+
for model in df.to_dict("records"):
|
|
33
|
+
# by default, process the modela as a create request. Can be override to change the behavior
|
|
34
|
+
try:
|
|
35
|
+
nb_added += self.process_model(model)
|
|
36
|
+
# https://django-reversion.readthedocs.io/en/stable/common-problems.html
|
|
37
|
+
except RevertError:
|
|
38
|
+
revert_errors += 1
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(e) # noqa: T201
|
|
41
|
+
errors += 1
|
|
42
|
+
self.message_user(
|
|
43
|
+
request,
|
|
44
|
+
_(
|
|
45
|
+
"Your CSV file has been imported : {} imported ({} added, {} updated), {} errors, {} revert errors found due to failure to restore old versions"
|
|
46
|
+
).format(
|
|
47
|
+
df.shape[0] - errors - revert_errors,
|
|
48
|
+
nb_added,
|
|
49
|
+
df.shape[0] - errors - revert_errors - nb_added,
|
|
50
|
+
errors,
|
|
51
|
+
revert_errors,
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
return redirect("..")
|
|
55
|
+
form = CsvImportForm()
|
|
56
|
+
payload = {"form": form}
|
|
57
|
+
return render(request, "wbcore/admin/csv_form.html", payload)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@admin.register(AbsenceRequestType)
|
|
61
|
+
class AbsenceRequestTypeAdmin(admin.ModelAdmin):
|
|
62
|
+
list_display = [
|
|
63
|
+
"title",
|
|
64
|
+
"is_vacation",
|
|
65
|
+
"is_timeoff",
|
|
66
|
+
"is_extensible",
|
|
67
|
+
"auto_approve",
|
|
68
|
+
"days_in_advance",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
autocomplete_fields = ["crossborder_countries", "extra_notify_groups"]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AbsenceRequestPeriodsInline(admin.TabularInline):
|
|
75
|
+
model = AbsenceRequestPeriods
|
|
76
|
+
ordering = ("timespan__startswith",)
|
|
77
|
+
extra = 0
|
|
78
|
+
raw_id_fields = ["balance", "request"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@admin.register(AbsenceRequest)
|
|
82
|
+
class AbsenceRequestAdmin(admin.ModelAdmin):
|
|
83
|
+
list_display = ["status", "employee", "period", "type", "_total_hours", "_total_vacation_hours"]
|
|
84
|
+
|
|
85
|
+
fieldsets = (
|
|
86
|
+
(
|
|
87
|
+
"",
|
|
88
|
+
{
|
|
89
|
+
"fields": (
|
|
90
|
+
("status", "type"),
|
|
91
|
+
("period", "employee"),
|
|
92
|
+
"attachment",
|
|
93
|
+
)
|
|
94
|
+
},
|
|
95
|
+
),
|
|
96
|
+
(
|
|
97
|
+
_("Notes"),
|
|
98
|
+
{"fields": ("notes", "reason")},
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
inlines = (AbsenceRequestPeriodsInline,)
|
|
102
|
+
autocomplete_fields = ["employee"]
|
|
103
|
+
list_filter = ["type__is_vacation"]
|
|
104
|
+
|
|
105
|
+
def _total_hours(self, obj):
|
|
106
|
+
return obj._total_hours
|
|
107
|
+
|
|
108
|
+
def _total_vacation_hours(self, obj):
|
|
109
|
+
return obj._total_vacation_hours
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# def get_queryset(self, request):
|
|
113
|
+
# return super().get_queryset().
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from ..models import (
|
|
4
|
+
DayOff,
|
|
5
|
+
DayOffCalendar,
|
|
6
|
+
DefaultDailyPeriod,
|
|
7
|
+
EmployeeWeeklyOffPeriods,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@admin.register(DayOffCalendar)
|
|
12
|
+
class DayOffCalendarModelAdmin(admin.ModelAdmin):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@admin.register(DayOff)
|
|
17
|
+
class DayOffAdmin(admin.ModelAdmin):
|
|
18
|
+
list_display = ["date", "title", "count_as_holiday", "calendar"]
|
|
19
|
+
ordering = ["date"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@admin.register(DefaultDailyPeriod)
|
|
23
|
+
class DefaultDailyPeriodAdmin(admin.ModelAdmin):
|
|
24
|
+
list_display = ["lower_time", "upper_time", "title", "total_hours"]
|
|
25
|
+
ordering = ["lower_time"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EmployeeWeeklyOffPeriodsInLine(admin.TabularInline):
|
|
29
|
+
model = EmployeeWeeklyOffPeriods
|
|
30
|
+
fields = [
|
|
31
|
+
"period",
|
|
32
|
+
"weekday",
|
|
33
|
+
]
|
|
34
|
+
fk_name = "employee"
|
|
35
|
+
extra = 0
|
|
36
|
+
raw_id_fields = ["employee"]
|
|
37
|
+
ordering = ("weekday", "period__lower_time")
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from ..models.employee import (
|
|
4
|
+
BalanceHourlyAllowance,
|
|
5
|
+
EmployeeHumanResource,
|
|
6
|
+
EmployeeYearBalance,
|
|
7
|
+
Position,
|
|
8
|
+
)
|
|
9
|
+
from .calendars import EmployeeWeeklyOffPeriodsInLine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PositionInline(admin.TabularInline):
|
|
13
|
+
model = Position
|
|
14
|
+
extra = 0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@admin.register(Position)
|
|
18
|
+
class PositionAdmin(admin.ModelAdmin):
|
|
19
|
+
search_fields = ("name",)
|
|
20
|
+
list_display = ("name", "height", "manager")
|
|
21
|
+
|
|
22
|
+
raw_id_fields = ["groups", "manager"]
|
|
23
|
+
autocomplete_fields = ["groups", "manager"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BalanceHourlyAllowanceTabularAdmin(admin.TabularInline):
|
|
27
|
+
model = BalanceHourlyAllowance
|
|
28
|
+
fk_name = "balance"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@admin.register(EmployeeYearBalance)
|
|
32
|
+
class EmployeeYearBalanceAdmin(admin.ModelAdmin):
|
|
33
|
+
inlines = [BalanceHourlyAllowanceTabularAdmin]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class EmployeeYearBalanceInline(admin.TabularInline):
|
|
37
|
+
model = EmployeeYearBalance
|
|
38
|
+
fields = (
|
|
39
|
+
"year",
|
|
40
|
+
"balance",
|
|
41
|
+
"daily_hours",
|
|
42
|
+
"number_mandatory_days_off",
|
|
43
|
+
"total_vacation_hourly_usage",
|
|
44
|
+
"total_vacation_hourly_balance",
|
|
45
|
+
"balance_in_days",
|
|
46
|
+
"number_mandatory_days_off_in_days",
|
|
47
|
+
"total_vacation_hourly_usage_in_days",
|
|
48
|
+
"total_vacation_hourly_balance_in_days",
|
|
49
|
+
)
|
|
50
|
+
readonly_fields = [
|
|
51
|
+
"balance",
|
|
52
|
+
"daily_hours",
|
|
53
|
+
"number_mandatory_days_off",
|
|
54
|
+
"total_vacation_hourly_usage",
|
|
55
|
+
"total_vacation_hourly_balance",
|
|
56
|
+
"balance_in_days",
|
|
57
|
+
"number_mandatory_days_off_in_days",
|
|
58
|
+
"total_vacation_hourly_usage_in_days",
|
|
59
|
+
"total_vacation_hourly_balance_in_days",
|
|
60
|
+
]
|
|
61
|
+
extra = 0
|
|
62
|
+
ordering = ("-year",)
|
|
63
|
+
show_change_link = True
|
|
64
|
+
|
|
65
|
+
def _number_mandatory_days_off(self, obj):
|
|
66
|
+
return obj._number_mandatory_days_off
|
|
67
|
+
|
|
68
|
+
def _total_vacation_hourly_usage(self, obj):
|
|
69
|
+
return obj._total_vacation_hourly_usage
|
|
70
|
+
|
|
71
|
+
def _total_vacation_hourly_balance(self, obj):
|
|
72
|
+
return obj._total_vacation_hourly_balance
|
|
73
|
+
|
|
74
|
+
def _balance_in_days(self, obj):
|
|
75
|
+
return obj._balance_in_days
|
|
76
|
+
|
|
77
|
+
def _number_mandatory_days_off_in_days(self, obj):
|
|
78
|
+
return obj._number_mandatory_days_off_in_days
|
|
79
|
+
|
|
80
|
+
def _total_vacation_hourly_usage_in_days(self, obj):
|
|
81
|
+
return obj._total_vacation_hourly_usage_in_days
|
|
82
|
+
|
|
83
|
+
def _total_vacation_hourly_balance_in_days(self, obj):
|
|
84
|
+
return obj._total_vacation_hourly_balance_in_days
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@admin.register(EmployeeHumanResource)
|
|
88
|
+
class EmployeeHumanResourceAdmin(admin.ModelAdmin):
|
|
89
|
+
fieldsets = (
|
|
90
|
+
(
|
|
91
|
+
"",
|
|
92
|
+
{
|
|
93
|
+
"fields": (
|
|
94
|
+
"profile",
|
|
95
|
+
"is_active",
|
|
96
|
+
"extra_days_frequency",
|
|
97
|
+
"occupancy_rate",
|
|
98
|
+
"contract_type",
|
|
99
|
+
"position",
|
|
100
|
+
"enrollment_at",
|
|
101
|
+
"calendar",
|
|
102
|
+
)
|
|
103
|
+
},
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
raw_id_fields = ("profile",)
|
|
107
|
+
search_fields = ("profile__computed_str",)
|
|
108
|
+
list_display = ("profile", "contract_type", "position", "calendar")
|
|
109
|
+
inlines = (EmployeeYearBalanceInline, EmployeeWeeklyOffPeriodsInLine)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from ..models.kpi import KPI, Evaluation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@admin.register(Evaluation)
|
|
7
|
+
class KEvaluationAdmin(admin.ModelAdmin):
|
|
8
|
+
list_display = ["id", "person", "evaluated_score", "evaluation_date", "evaluated_period"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EvaluationInline(admin.TabularInline):
|
|
12
|
+
model = Evaluation
|
|
13
|
+
extra = 0
|
|
14
|
+
raw_id_fields = ["kpi", "person"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@admin.register(KPI)
|
|
18
|
+
class KPIAdmin(admin.ModelAdmin):
|
|
19
|
+
list_display = ["name", "period", "goal"]
|
|
20
|
+
inlines = (EvaluationInline,)
|
|
21
|
+
raw_id_fields = ["evaluated_persons"]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from django.contrib import admin
|
|
5
|
+
from django.shortcuts import redirect, render
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
from reversion.errors import RevertError
|
|
8
|
+
from wbcore.admin import CsvImportForm, ExportCsvMixin, ImportCsvMixin
|
|
9
|
+
|
|
10
|
+
from ..models.review import (
|
|
11
|
+
Review,
|
|
12
|
+
ReviewAnswer,
|
|
13
|
+
ReviewGroup,
|
|
14
|
+
ReviewQuestion,
|
|
15
|
+
ReviewQuestionCategory,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CustomImportCsvMixin(ImportCsvMixin):
|
|
20
|
+
def _import_csv(self, request, _sep=";"):
|
|
21
|
+
if request.method == "POST":
|
|
22
|
+
csv_file = request.FILES["csv_file"]
|
|
23
|
+
|
|
24
|
+
str_text = ""
|
|
25
|
+
for line in csv_file:
|
|
26
|
+
str_text = str_text + line.decode()
|
|
27
|
+
# Import csv as df
|
|
28
|
+
df = pd.read_csv(StringIO(str_text), sep=_sep)
|
|
29
|
+
# Sanitize dataframe
|
|
30
|
+
df = df.where(pd.notnull(df), None)
|
|
31
|
+
df = df.drop(df.columns.difference(self.get_import_fields()), axis=1)
|
|
32
|
+
|
|
33
|
+
# Overide this function if there is foreign key ids in the dataframe
|
|
34
|
+
df = self.manipulate_df(df)
|
|
35
|
+
errors = 0
|
|
36
|
+
revert_errors = 0
|
|
37
|
+
nb_added = 0
|
|
38
|
+
for model in df.to_dict("records"):
|
|
39
|
+
# by default, process the modela as a create request. Can be override to change the behavior
|
|
40
|
+
try:
|
|
41
|
+
nb_added += self.process_model(model)
|
|
42
|
+
# https://django-reversion.readthedocs.io/en/stable/common-problems.html
|
|
43
|
+
except RevertError:
|
|
44
|
+
revert_errors += 1
|
|
45
|
+
except Exception as e:
|
|
46
|
+
print(e) # noqa: T201
|
|
47
|
+
errors += 1
|
|
48
|
+
self.message_user(
|
|
49
|
+
request,
|
|
50
|
+
_(
|
|
51
|
+
"Your CSV file has been imported : {} imported ({} added, {} updated), {} errors, {} revert errors found due to failure to restore old versions"
|
|
52
|
+
).format(
|
|
53
|
+
df.shape[0] - errors - revert_errors,
|
|
54
|
+
nb_added,
|
|
55
|
+
df.shape[0] - errors - revert_errors - nb_added,
|
|
56
|
+
errors,
|
|
57
|
+
revert_errors,
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
return redirect("..")
|
|
61
|
+
form = CsvImportForm()
|
|
62
|
+
payload = {"form": form}
|
|
63
|
+
return render(request, "wbcore/admin/csv_form.html", payload)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@admin.register(ReviewGroup)
|
|
67
|
+
class ReviewGroupAdmin(admin.ModelAdmin):
|
|
68
|
+
list_display = ["name"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@admin.register(Review)
|
|
72
|
+
class ReviewAdmin(admin.ModelAdmin):
|
|
73
|
+
list_display = [
|
|
74
|
+
"id",
|
|
75
|
+
"year",
|
|
76
|
+
"type",
|
|
77
|
+
"from_date",
|
|
78
|
+
"to_date",
|
|
79
|
+
"review_deadline",
|
|
80
|
+
"review",
|
|
81
|
+
"auto_apply_deadline",
|
|
82
|
+
"status",
|
|
83
|
+
"reviewee",
|
|
84
|
+
"reviewer",
|
|
85
|
+
"moderator",
|
|
86
|
+
"review_group",
|
|
87
|
+
"is_template",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@admin.register(ReviewQuestionCategory)
|
|
92
|
+
class ReviewQuestionCategoryAdmin(ExportCsvMixin, CustomImportCsvMixin, admin.ModelAdmin):
|
|
93
|
+
list_display = ["name", "order", "weight"]
|
|
94
|
+
|
|
95
|
+
def manipulate_df(self, df):
|
|
96
|
+
return df
|
|
97
|
+
|
|
98
|
+
def get_import_fields(self):
|
|
99
|
+
return ["name", "order", "weight"]
|
|
100
|
+
|
|
101
|
+
def process_model(self, model):
|
|
102
|
+
if category_name := model.get("name"):
|
|
103
|
+
_, created = self.model.objects.update_or_create(
|
|
104
|
+
name=category_name,
|
|
105
|
+
defaults={
|
|
106
|
+
"order": model.get("order"),
|
|
107
|
+
"weight": model.get("weight"),
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
return 1 if created else 0
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@admin.register(ReviewQuestion)
|
|
115
|
+
class ReviewQuestion(ExportCsvMixin, CustomImportCsvMixin, admin.ModelAdmin):
|
|
116
|
+
list_display = [
|
|
117
|
+
"id",
|
|
118
|
+
"question",
|
|
119
|
+
"review",
|
|
120
|
+
"category",
|
|
121
|
+
"mandatory",
|
|
122
|
+
"answer_type",
|
|
123
|
+
"for_reviewee",
|
|
124
|
+
"for_reviewer",
|
|
125
|
+
"for_department_peers",
|
|
126
|
+
"for_company_peers",
|
|
127
|
+
"order",
|
|
128
|
+
"weight",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
def manipulate_df(self, df):
|
|
132
|
+
df["review"] = df["review_id"].apply(lambda x: Review.objects.get(id=x))
|
|
133
|
+
df["category"] = df["category_name"].apply(lambda x: ReviewQuestionCategory.objects.filter(name=x).first())
|
|
134
|
+
return df
|
|
135
|
+
|
|
136
|
+
def get_import_fields(self):
|
|
137
|
+
return ["review_id", "category_name", "answer_type", "order", "weight", "question"]
|
|
138
|
+
|
|
139
|
+
def process_model(self, model):
|
|
140
|
+
if review := model.get("review"):
|
|
141
|
+
_, created = self.model.objects.update_or_create(
|
|
142
|
+
review=review,
|
|
143
|
+
question=model.get("question"),
|
|
144
|
+
defaults={
|
|
145
|
+
"category": model.get("category"),
|
|
146
|
+
"answer_type": model.get("answer_type"),
|
|
147
|
+
"order": model.get("order"),
|
|
148
|
+
"weight": model.get("weight"),
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
return 1 if created else 0
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@admin.register(ReviewAnswer)
|
|
156
|
+
class ReviewAnswerAdmin(admin.ModelAdmin):
|
|
157
|
+
list_display = ["question", "answered_by", "answer_number", "answered_anonymized"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from django.apps import AppConfig, apps
|
|
2
|
+
from django.db.models.signals import post_migrate
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class WbhumanResourcesConfig(AppConfig):
|
|
6
|
+
name = "wbhuman_resources"
|
|
7
|
+
|
|
8
|
+
def ready(self) -> None:
|
|
9
|
+
from wbcore.signals.filters import add_filters
|
|
10
|
+
from wbhuman_resources.management import initialize_task
|
|
11
|
+
|
|
12
|
+
from .filters.signals import add_position_filter
|
|
13
|
+
|
|
14
|
+
if apps.is_installed("wbcrm"):
|
|
15
|
+
from wbcrm.filters import ActivityFilter
|
|
16
|
+
|
|
17
|
+
add_filters.connect(add_position_filter, sender=ActivityFilter)
|
|
18
|
+
|
|
19
|
+
post_migrate.connect(
|
|
20
|
+
initialize_task,
|
|
21
|
+
dispatch_uid="wbhuman_resources.initialize_task",
|
|
22
|
+
)
|