ositah 25.6.dev1__py3-none-any.whl → 25.9.dev2__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.

Potentially problematic release.


This version of ositah might be problematic. Click here for more details.

Files changed (42) hide show
  1. ositah/app.py +17 -17
  2. ositah/apps/analysis.py +785 -785
  3. ositah/apps/configuration/callbacks.py +916 -916
  4. ositah/apps/configuration/main.py +546 -546
  5. ositah/apps/configuration/parameters.py +74 -74
  6. ositah/apps/configuration/tools.py +112 -112
  7. ositah/apps/export.py +1209 -1191
  8. ositah/apps/validation/callbacks.py +240 -240
  9. ositah/apps/validation/main.py +89 -89
  10. ositah/apps/validation/parameters.py +25 -25
  11. ositah/apps/validation/tables.py +646 -646
  12. ositah/apps/validation/tools.py +552 -552
  13. ositah/assets/arrow_down_up.svg +3 -3
  14. ositah/assets/ositah.css +53 -53
  15. ositah/assets/sort_ascending.svg +4 -4
  16. ositah/assets/sort_descending.svg +5 -5
  17. ositah/assets/sorttable.js +499 -499
  18. ositah/main.py +449 -449
  19. ositah/ositah.example.cfg +229 -229
  20. ositah/static/style.css +53 -53
  21. ositah/templates/base.html +22 -22
  22. ositah/templates/bootstrap_login.html +38 -38
  23. ositah/templates/login_form.html +26 -26
  24. ositah/utils/agents.py +124 -124
  25. ositah/utils/authentication.py +287 -287
  26. ositah/utils/cache.py +19 -19
  27. ositah/utils/core.py +13 -13
  28. ositah/utils/exceptions.py +64 -64
  29. ositah/utils/hito_db.py +51 -51
  30. ositah/utils/hito_db_model.py +253 -253
  31. ositah/utils/menus.py +339 -339
  32. ositah/utils/period.py +139 -139
  33. ositah/utils/projects.py +1179 -1178
  34. ositah/utils/teams.py +42 -42
  35. ositah/utils/utils.py +474 -474
  36. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/METADATA +149 -150
  37. ositah-25.9.dev2.dist-info/RECORD +46 -0
  38. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/licenses/LICENSE +29 -29
  39. ositah-25.6.dev1.dist-info/RECORD +0 -46
  40. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/WHEEL +0 -0
  41. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/entry_points.txt +0 -0
  42. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/top_level.txt +0 -0
@@ -1,546 +1,546 @@
1
- """
2
- OSITAH subapplication to manage the configuration
3
-
4
- This file contains only the layout definitions
5
- """
6
-
7
- from datetime import date
8
-
9
- from dash import dcc
10
-
11
- from ositah.apps.configuration.callbacks import *
12
- from ositah.utils.menus import TABLE_TYPE_DUMMY_STORE
13
- from ositah.utils.projects import MASTERPROJECT_DELETED_ACTIVITY
14
- from ositah.utils.utils import AUTHORIZED_ROLES, HITO_ROLE_PROJECT_MGR
15
-
16
-
17
- def configuration_layout():
18
- """
19
- Build the layout for this application, after reading the data if necessary.
20
-
21
- :return: application layout
22
- """
23
-
24
- return html.Div(
25
- [
26
- html.H1("Configuration OSITAH"),
27
- html.Div(
28
- configuration_submenus(),
29
- id="configuration-submenus",
30
- style={"marginTop": "3em"},
31
- ),
32
- # The following dcc.Store coupled with tables must be created in the layout for
33
- # the callback to work
34
- dcc.Store(
35
- id={"type": TABLE_TYPE_DUMMY_STORE, "id": TABLE_NSIP_PROJECT_SYNC_ID},
36
- data=0,
37
- ),
38
- ]
39
- )
40
-
41
-
42
- def declaration_periods_layout():
43
- """
44
- Define the layout specific to the management of declaration periods
45
-
46
- :return: html.Div component
47
- """
48
-
49
- declaration_periods = get_declaration_periods(descending=False)
50
- declaration_period_items = []
51
- i = 0
52
- for period in declaration_periods:
53
- declaration_period_items.append({"label": period.name, "value": i})
54
- i += 1
55
-
56
- # Disable creation of a new (next) period if the period matching the current already exists
57
- today = date.today().isoformat()
58
- if (
59
- len(declaration_periods) > 0
60
- and today >= declaration_periods[-1].start_date
61
- and today <= declaration_periods[-1].end_date
62
- ):
63
- current_period_exists = True
64
- else:
65
- current_period_exists = False
66
-
67
- period_params = [
68
- dbc.Row(
69
- [
70
- dbc.Label(
71
- "Date de début",
72
- html_for=DECLARATION_PERIOD_START_DATE_ID,
73
- style={"fontWeight": "bold"},
74
- width="5",
75
- ),
76
- dbc.Col(
77
- dcc.DatePickerSingle(
78
- id=DECLARATION_PERIOD_START_DATE_ID,
79
- month_format="MMMM Y",
80
- placeholder="MMMM Y",
81
- ),
82
- width="auto",
83
- class_name="me-3",
84
- ),
85
- ],
86
- style={"marginBottom": "10px"},
87
- ),
88
- dbc.Row(
89
- [
90
- dbc.Label(
91
- "Date de validation",
92
- html_for=DECLARATION_PERIOD_VALIDATION_DATE_ID,
93
- style={"fontWeight": "bold"},
94
- width="5",
95
- ),
96
- dbc.Col(
97
- dcc.DatePickerSingle(
98
- id=DECLARATION_PERIOD_VALIDATION_DATE_ID,
99
- month_format="MMMM Y",
100
- placeholder="MMMM Y",
101
- ),
102
- width="auto",
103
- class_name="me-3",
104
- ),
105
- ],
106
- style={"marginBottom": "10px"},
107
- ),
108
- dbc.Row(
109
- [
110
- dbc.Label(
111
- "Date de fin",
112
- html_for=DECLARATION_PERIOD_END_DATE_ID,
113
- style={"fontWeight": "bold"},
114
- width="5",
115
- ),
116
- dbc.Col(
117
- dcc.DatePickerSingle(
118
- id=DECLARATION_PERIOD_END_DATE_ID,
119
- month_format="MMMM Y",
120
- placeholder="MMMM Y",
121
- ),
122
- width="auto",
123
- class_name="me-3",
124
- ),
125
- ],
126
- style={"marginBottom": "10px"},
127
- ),
128
- dbc.Row(
129
- [
130
- dbc.Label(
131
- "Nom",
132
- html_for=DECLARATION_PERIOD_NAME_ID,
133
- style={"fontWeight": "bold"},
134
- width="5",
135
- ),
136
- dbc.Col(
137
- dbc.Input(
138
- type="start_date",
139
- id=DECLARATION_PERIOD_NAME_ID,
140
- placeholder="Nom de la période",
141
- ),
142
- width="6",
143
- class_name="me-3",
144
- ),
145
- ],
146
- ),
147
- ]
148
-
149
- return html.Div(
150
- [
151
- html.P(),
152
- dbc.Alert(id=DECLARATION_PERIODS_STATUS_ID, is_open=False),
153
- dbc.Row(
154
- [
155
- dbc.Col(
156
- [
157
- dbc.Label(html.B("Périodes")),
158
- dbc.Select(
159
- id=DECLARATION_PERIODS_ID,
160
- options=declaration_period_items,
161
- html_size=DECLARATION_PERIODS_LIST_MAX_SIZE,
162
- ),
163
- ],
164
- width={"offset": 1, "size": 3},
165
- ),
166
- dbc.Col(
167
- dbc.Form(period_params),
168
- id=DECLARATION_PERIOD_PARAMS_ID,
169
- style={
170
- "border": "1px solid",
171
- "borderRadius": "4px",
172
- "borderSizing": "border-box",
173
- "padding": "10px 10px",
174
- "visibility": "hidden",
175
- },
176
- width={"offset": 1, "size": 4},
177
- ),
178
- ],
179
- align="center",
180
- justify="start",
181
- ),
182
- dbc.Row(
183
- [
184
- dbc.Col(
185
- html.Div(
186
- dbc.Button(
187
- "Nouveau semestre",
188
- id=DECLARATION_PERIODS_CREATE_NEW_ID,
189
- disabled=current_period_exists,
190
- ),
191
- id=DECLARATION_PERIODS_CREATE_DIV_ID,
192
- ),
193
- width={"size": 3, "offset": 0},
194
- ),
195
- dbc.Col(
196
- dbc.Button(
197
- "Valider",
198
- id=DECLARATION_PERIODS_SAVE_NEW_ID,
199
- disabled=True,
200
- ),
201
- width={"size": 3, "offset": 0},
202
- ),
203
- dbc.Tooltip(
204
- (
205
- "Le semestre courant existe déjà"
206
- if current_period_exists
207
- else "Ajoute le semestre courant"
208
- ),
209
- target=DECLARATION_PERIODS_CREATE_DIV_ID,
210
- placement="bottom",
211
- ),
212
- ],
213
- justify="evenly",
214
- style={"marginTop": "3em"},
215
- ),
216
- dcc.Store(id=DECLARATION_PERIODS_CREATE_CLICK_ID, data=0),
217
- # The 2 following Stores are used to control if the status must be displayed or not.
218
- # If DECLARATION_PERIODS_STATUS_VISIBLE_ID > DECLARATION_PERIODS_STATUS_HIDDEN_ID,
219
- # it must be displayed else it must be hidden.
220
- dcc.Store(id=DECLARATION_PERIODS_STATUS_HIDDEN_ID, data=-0),
221
- dcc.Store(id=DECLARATION_PERIODS_STATUS_VISIBLE_ID, data=0),
222
- ]
223
- )
224
-
225
-
226
- def nsip_sync_layout():
227
- """
228
- Define the layout specific to the NSIP project synchronisation
229
-
230
- :return: html.Div component
231
- """
232
-
233
- return html.Div(
234
- [
235
- html.P(),
236
- dbc.Row(
237
- [
238
- dbc.Col(
239
- dbc.RadioItems(
240
- id=NSIP_SYNC_ACTIVITY_TYPE_ID,
241
- options=[
242
- {
243
- "label": "Projets",
244
- "value": NSIP_SYNC_ACTIVITY_TYPE_PROJECT,
245
- },
246
- {
247
- "label": "Autres activités",
248
- "value": NSIP_SYNC_ACTIVITY_TYPE_OTHER,
249
- },
250
- ],
251
- value=NSIP_SYNC_ACTIVITY_TYPE_PROJECT,
252
- ),
253
- width={"offset": 1, "size": 2},
254
- ),
255
- dbc.Col(
256
- dbc.Button(
257
- "Differences OSITAH/NSIP",
258
- id=NSIP_SYNC_SHOW_DIFF_ID,
259
- color="secondary",
260
- className="me-1",
261
- ),
262
- width={"offset": 1, "size": 3},
263
- ),
264
- dbc.Col(
265
- dbc.Button(
266
- "Mise à jour OSITAH",
267
- id=NSIP_SYNC_APPLY_DIFF_ID,
268
- color="secondary",
269
- className="me-1",
270
- disabled=True,
271
- ),
272
- width={"offset": 1, "size": 2},
273
- ),
274
- ]
275
- ),
276
- html.P(),
277
- html.Div(id=NSIP_SYNC_CONTENT_ID),
278
- ]
279
- )
280
-
281
-
282
- def project_teams_layout():
283
- """
284
- Define the layout specific to the configuration of project teams
285
-
286
- :return: html.Div component
287
- """
288
-
289
- activities = get_all_hito_activities(True)
290
- masterproject_items = []
291
- for masterproject in sorted(activities.masterproject.unique(), key=lambda x: x.upper()):
292
- if masterproject != MASTERPROJECT_DELETED_ACTIVITY:
293
- masterproject_items.append({"label": masterproject, "value": masterproject})
294
-
295
- return html.Div(
296
- [
297
- html.P(),
298
- dbc.Alert(id=ACTIVITY_TEAMS_STATUS_ID, is_open=False),
299
- dbc.Row(
300
- [
301
- dbc.Col(
302
- [
303
- dbc.Label(html.B("Masterprojet")),
304
- dbc.Select(
305
- id=ACTIVITY_TEAMS_MASTERPROJECTS_ID,
306
- options=masterproject_items,
307
- html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
308
- ),
309
- ],
310
- width={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH, "offset": 0},
311
- xxl={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL, "offset": 0},
312
- ),
313
- dbc.Col(
314
- [
315
- dbc.Label(html.B("Projet")),
316
- dbc.Select(
317
- id=ACTIVITY_TEAMS_PROJECTS_ID,
318
- html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
319
- ),
320
- ],
321
- width={
322
- "size": ACTIVITY_TEAMS_LIST_BOX_WIDTH,
323
- "offset": ACTIVITY_TEAMS_LIST_BOX_INTERVAL,
324
- },
325
- xxl={
326
- "size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL,
327
- "offset": ACTIVITY_TEAMS_LIST_BOX_INTERVAL,
328
- },
329
- ),
330
- ],
331
- justify="start",
332
- ),
333
- dbc.Row(
334
- [
335
- dbc.Col(
336
- [
337
- dbc.Label(html.B("Equipes IJCLab")),
338
- dbc.Select(
339
- id=ACTIVITY_TEAMS_LAB_TEAMS_ID,
340
- html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
341
- ),
342
- ],
343
- width={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH, "offset": 0},
344
- xxl={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL, "offset": 0},
345
- ),
346
- dbc.Col(
347
- html.Div(
348
- [
349
- dbc.ButtonGroup(
350
- [
351
- dbc.Button(
352
- html.I(className="bi bi-chevron-double-right"),
353
- id=ACTIVITY_TEAMS_BUTTON_ADD_ID,
354
- disabled=True,
355
- style={"marginTop": "1em"},
356
- ),
357
- dbc.Button(
358
- html.I(className="bi bi-chevron-double-left"),
359
- id=ACTIVITY_TEAMS_BUTTON_REMOVE_ID,
360
- disabled=True,
361
- style={"marginTop": "2em"},
362
- ),
363
- ],
364
- vertical=True,
365
- ),
366
- ],
367
- style={"display": "flex", "justifyContent": "center"},
368
- ),
369
- width=ACTIVITY_TEAMS_LIST_BOX_INTERVAL,
370
- align="center",
371
- ),
372
- dbc.Col(
373
- [
374
- dbc.Label(html.B("Equipes du projet")),
375
- dbc.Select(
376
- id=ACTIVITY_TEAMS_SELECTED_TEAMS_ID,
377
- html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
378
- ),
379
- ],
380
- width={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH, "offset": 0},
381
- xxl={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL, "offset": 0},
382
- ),
383
- ],
384
- justify="start",
385
- style={"marginTop": "5em"},
386
- ),
387
- dbc.Row(
388
- [
389
- dbc.Col(
390
- dbc.Button(
391
- "Mettre à jour",
392
- id=ACTIVITY_TEAMS_BUTTON_UPDATE_ID,
393
- disabled=True,
394
- ),
395
- width={"size": 3, "offset": 0},
396
- ),
397
- dbc.Col(
398
- dbc.Button(
399
- "Annuler",
400
- id=ACTIVITY_TEAMS_BUTTON_CANCEL_ID,
401
- disabled=True,
402
- ),
403
- width={"size": 3, "offset": 0},
404
- ),
405
- ],
406
- justify="evenly",
407
- style={"marginTop": "3em"},
408
- ),
409
- dcc.Store(id=ACTIVITY_TEAMS_PROJECT_ACTIVITY_ID, data=True),
410
- dcc.Store(id=ACTIVITY_TEAMS_ADDED_TEAMS_ID, data=[]),
411
- dcc.Store(id=ACTIVITY_TEAMS_REMOVED_TEAMS_ID, data=[]),
412
- dcc.Store(id=ACTIVITY_TEAMS_RESET_INDICATOR_ID, data=0),
413
- ],
414
- )
415
-
416
-
417
- def project_mgt_layout():
418
- """
419
- Build the layout for the project management tab.
420
-
421
- :return: html.Div component
422
- """
423
-
424
- masterproject_list = []
425
- project_list = []
426
- return html.Div(
427
- [
428
- html.P(),
429
- dbc.Alert(id=PROJECT_MGT_STATUS_ID, is_open=False),
430
- dbc.Row(
431
- [
432
- dbc.RadioItems(
433
- id=PROJECT_MGT_PROJECT_TYPE_ID,
434
- options=[
435
- {
436
- "label": "Projets NSIP",
437
- "value": PROJECT_MGT_PROJECT_TYPE_NSIP,
438
- },
439
- {
440
- "label": "Projets locaux",
441
- "value": PROJECT_MGT_PROJECT_TYPE_LOCAL,
442
- },
443
- {
444
- "label": "Projets désactivés",
445
- "value": PROJECT_MGT_PROJECT_TYPE_DISABLED,
446
- },
447
- ],
448
- inline=True,
449
- ),
450
- ],
451
- justify="center",
452
- style={"marginTop": "1em"},
453
- ),
454
- dbc.Row(
455
- [
456
- dbc.Col(
457
- [
458
- dbc.Label(html.B("Masterprojets")),
459
- dbc.Select(
460
- id=PROJECT_MGT_MASTERPROJECT_LIST_ID,
461
- options=masterproject_list,
462
- html_size=PROJECT_MGT_LIST_MAX_SIZE,
463
- ),
464
- ],
465
- width={"size": PROJECT_MGT_LIST_BOX_WIDTH, "offset": 0},
466
- xxl={"size": PROJECT_MGT_LIST_BOX_WIDTH_XXL, "offset": 0},
467
- ),
468
- dbc.Col(
469
- [
470
- dbc.Label(html.B("Projets")),
471
- dbc.Select(
472
- id=PROJECT_MGT_PROJECT_LIST_ID,
473
- options=project_list,
474
- html_size=PROJECT_MGT_LIST_MAX_SIZE,
475
- ),
476
- ],
477
- width={"size": PROJECT_MGT_LIST_BOX_WIDTH, "offset": 0},
478
- xxl={"size": PROJECT_MGT_LIST_BOX_WIDTH_XXL, "offset": 0},
479
- ),
480
- ],
481
- style={"marginTop": "1em"},
482
- ),
483
- dbc.Row(
484
- dbc.Col(
485
- dbc.Button(
486
- "",
487
- id=PROJECT_MGT_ACTION_BUTTON_ID,
488
- disabled=True,
489
- style={"visibility": "hidden"},
490
- ),
491
- ),
492
- style={"marginTop": "3em"},
493
- ),
494
- dcc.Store(id=PROJECT_MGT_PROJECT_ACTIVITY_ID, data=True),
495
- ],
496
- )
497
-
498
-
499
- def configuration_submenus():
500
- """
501
- Build the tabs menus of the configuration subapplication
502
-
503
- :return: DBC Tabs
504
- """
505
-
506
- global_params = GlobalParams()
507
- if AUTHORIZED_ROLES.index(global_params.session_data.role) > AUTHORIZED_ROLES.index(
508
- HITO_ROLE_PROJECT_MGR
509
- ):
510
- nsip_sync_disabled = True
511
- declaration_period_disabled = True
512
- project_mgt_disabled = True
513
- else:
514
- nsip_sync_disabled = False
515
- declaration_period_disabled = False
516
- project_mgt_disabled = False
517
-
518
- return dbc.Tabs(
519
- [
520
- dbc.Tab(
521
- id=TAB_ID_ACTIVITY_TEAMS,
522
- tab_id=TAB_ID_ACTIVITY_TEAMS,
523
- label="Equipes des projets",
524
- ),
525
- dbc.Tab(
526
- id=TAB_ID_NSIP_PROJECT_SYNC,
527
- tab_id=TAB_ID_NSIP_PROJECT_SYNC,
528
- label="Synchronisation NSIP",
529
- disabled=nsip_sync_disabled,
530
- ),
531
- dbc.Tab(
532
- id=TAB_ID_DECLARATION_PERIODS,
533
- tab_id=TAB_ID_DECLARATION_PERIODS,
534
- label="Périodes de déclaration",
535
- disabled=declaration_period_disabled,
536
- ),
537
- dbc.Tab(
538
- id=TAB_ID_PROJECT_MGT,
539
- tab_id=TAB_ID_PROJECT_MGT,
540
- label="Projets",
541
- disabled=project_mgt_disabled,
542
- ),
543
- ],
544
- id=CONFIGURATION_TAB_MENU_ID,
545
- persistence=True,
546
- )
1
+ """
2
+ OSITAH subapplication to manage the configuration
3
+
4
+ This file contains only the layout definitions
5
+ """
6
+
7
+ from datetime import date
8
+
9
+ from dash import dcc
10
+
11
+ from ositah.apps.configuration.callbacks import *
12
+ from ositah.utils.menus import TABLE_TYPE_DUMMY_STORE
13
+ from ositah.utils.projects import MASTERPROJECT_DELETED_ACTIVITY
14
+ from ositah.utils.utils import AUTHORIZED_ROLES, HITO_ROLE_PROJECT_MGR
15
+
16
+
17
+ def configuration_layout():
18
+ """
19
+ Build the layout for this application, after reading the data if necessary.
20
+
21
+ :return: application layout
22
+ """
23
+
24
+ return html.Div(
25
+ [
26
+ html.H1("Configuration OSITAH"),
27
+ html.Div(
28
+ configuration_submenus(),
29
+ id="configuration-submenus",
30
+ style={"marginTop": "3em"},
31
+ ),
32
+ # The following dcc.Store coupled with tables must be created in the layout for
33
+ # the callback to work
34
+ dcc.Store(
35
+ id={"type": TABLE_TYPE_DUMMY_STORE, "id": TABLE_NSIP_PROJECT_SYNC_ID},
36
+ data=0,
37
+ ),
38
+ ]
39
+ )
40
+
41
+
42
+ def declaration_periods_layout():
43
+ """
44
+ Define the layout specific to the management of declaration periods
45
+
46
+ :return: html.Div component
47
+ """
48
+
49
+ declaration_periods = get_declaration_periods(descending=False)
50
+ declaration_period_items = []
51
+ i = 0
52
+ for period in declaration_periods:
53
+ declaration_period_items.append({"label": period.name, "value": i})
54
+ i += 1
55
+
56
+ # Disable creation of a new (next) period if the period matching the current already exists
57
+ today = date.today().isoformat()
58
+ if (
59
+ len(declaration_periods) > 0
60
+ and today >= declaration_periods[-1].start_date
61
+ and today <= declaration_periods[-1].end_date
62
+ ):
63
+ current_period_exists = True
64
+ else:
65
+ current_period_exists = False
66
+
67
+ period_params = [
68
+ dbc.Row(
69
+ [
70
+ dbc.Label(
71
+ "Date de début",
72
+ html_for=DECLARATION_PERIOD_START_DATE_ID,
73
+ style={"fontWeight": "bold"},
74
+ width="5",
75
+ ),
76
+ dbc.Col(
77
+ dcc.DatePickerSingle(
78
+ id=DECLARATION_PERIOD_START_DATE_ID,
79
+ month_format="MMMM Y",
80
+ placeholder="MMMM Y",
81
+ ),
82
+ width="auto",
83
+ class_name="me-3",
84
+ ),
85
+ ],
86
+ style={"marginBottom": "10px"},
87
+ ),
88
+ dbc.Row(
89
+ [
90
+ dbc.Label(
91
+ "Date de validation",
92
+ html_for=DECLARATION_PERIOD_VALIDATION_DATE_ID,
93
+ style={"fontWeight": "bold"},
94
+ width="5",
95
+ ),
96
+ dbc.Col(
97
+ dcc.DatePickerSingle(
98
+ id=DECLARATION_PERIOD_VALIDATION_DATE_ID,
99
+ month_format="MMMM Y",
100
+ placeholder="MMMM Y",
101
+ ),
102
+ width="auto",
103
+ class_name="me-3",
104
+ ),
105
+ ],
106
+ style={"marginBottom": "10px"},
107
+ ),
108
+ dbc.Row(
109
+ [
110
+ dbc.Label(
111
+ "Date de fin",
112
+ html_for=DECLARATION_PERIOD_END_DATE_ID,
113
+ style={"fontWeight": "bold"},
114
+ width="5",
115
+ ),
116
+ dbc.Col(
117
+ dcc.DatePickerSingle(
118
+ id=DECLARATION_PERIOD_END_DATE_ID,
119
+ month_format="MMMM Y",
120
+ placeholder="MMMM Y",
121
+ ),
122
+ width="auto",
123
+ class_name="me-3",
124
+ ),
125
+ ],
126
+ style={"marginBottom": "10px"},
127
+ ),
128
+ dbc.Row(
129
+ [
130
+ dbc.Label(
131
+ "Nom",
132
+ html_for=DECLARATION_PERIOD_NAME_ID,
133
+ style={"fontWeight": "bold"},
134
+ width="5",
135
+ ),
136
+ dbc.Col(
137
+ dbc.Input(
138
+ type="start_date",
139
+ id=DECLARATION_PERIOD_NAME_ID,
140
+ placeholder="Nom de la période",
141
+ ),
142
+ width="6",
143
+ class_name="me-3",
144
+ ),
145
+ ],
146
+ ),
147
+ ]
148
+
149
+ return html.Div(
150
+ [
151
+ html.P(),
152
+ dbc.Alert(id=DECLARATION_PERIODS_STATUS_ID, is_open=False),
153
+ dbc.Row(
154
+ [
155
+ dbc.Col(
156
+ [
157
+ dbc.Label(html.B("Périodes")),
158
+ dbc.Select(
159
+ id=DECLARATION_PERIODS_ID,
160
+ options=declaration_period_items,
161
+ html_size=DECLARATION_PERIODS_LIST_MAX_SIZE,
162
+ ),
163
+ ],
164
+ width={"offset": 1, "size": 3},
165
+ ),
166
+ dbc.Col(
167
+ dbc.Form(period_params),
168
+ id=DECLARATION_PERIOD_PARAMS_ID,
169
+ style={
170
+ "border": "1px solid",
171
+ "borderRadius": "4px",
172
+ "borderSizing": "border-box",
173
+ "padding": "10px 10px",
174
+ "visibility": "hidden",
175
+ },
176
+ width={"offset": 1, "size": 4},
177
+ ),
178
+ ],
179
+ align="center",
180
+ justify="start",
181
+ ),
182
+ dbc.Row(
183
+ [
184
+ dbc.Col(
185
+ html.Div(
186
+ dbc.Button(
187
+ "Nouveau semestre",
188
+ id=DECLARATION_PERIODS_CREATE_NEW_ID,
189
+ disabled=current_period_exists,
190
+ ),
191
+ id=DECLARATION_PERIODS_CREATE_DIV_ID,
192
+ ),
193
+ width={"size": 3, "offset": 0},
194
+ ),
195
+ dbc.Col(
196
+ dbc.Button(
197
+ "Valider",
198
+ id=DECLARATION_PERIODS_SAVE_NEW_ID,
199
+ disabled=True,
200
+ ),
201
+ width={"size": 3, "offset": 0},
202
+ ),
203
+ dbc.Tooltip(
204
+ (
205
+ "Le semestre courant existe déjà"
206
+ if current_period_exists
207
+ else "Ajoute le semestre courant"
208
+ ),
209
+ target=DECLARATION_PERIODS_CREATE_DIV_ID,
210
+ placement="bottom",
211
+ ),
212
+ ],
213
+ justify="evenly",
214
+ style={"marginTop": "3em"},
215
+ ),
216
+ dcc.Store(id=DECLARATION_PERIODS_CREATE_CLICK_ID, data=0),
217
+ # The 2 following Stores are used to control if the status must be displayed or not.
218
+ # If DECLARATION_PERIODS_STATUS_VISIBLE_ID > DECLARATION_PERIODS_STATUS_HIDDEN_ID,
219
+ # it must be displayed else it must be hidden.
220
+ dcc.Store(id=DECLARATION_PERIODS_STATUS_HIDDEN_ID, data=-0),
221
+ dcc.Store(id=DECLARATION_PERIODS_STATUS_VISIBLE_ID, data=0),
222
+ ]
223
+ )
224
+
225
+
226
+ def nsip_sync_layout():
227
+ """
228
+ Define the layout specific to the NSIP project synchronisation
229
+
230
+ :return: html.Div component
231
+ """
232
+
233
+ return html.Div(
234
+ [
235
+ html.P(),
236
+ dbc.Row(
237
+ [
238
+ dbc.Col(
239
+ dbc.RadioItems(
240
+ id=NSIP_SYNC_ACTIVITY_TYPE_ID,
241
+ options=[
242
+ {
243
+ "label": "Projets",
244
+ "value": NSIP_SYNC_ACTIVITY_TYPE_PROJECT,
245
+ },
246
+ {
247
+ "label": "Autres activités",
248
+ "value": NSIP_SYNC_ACTIVITY_TYPE_OTHER,
249
+ },
250
+ ],
251
+ value=NSIP_SYNC_ACTIVITY_TYPE_PROJECT,
252
+ ),
253
+ width={"offset": 1, "size": 2},
254
+ ),
255
+ dbc.Col(
256
+ dbc.Button(
257
+ "Differences OSITAH/NSIP",
258
+ id=NSIP_SYNC_SHOW_DIFF_ID,
259
+ color="secondary",
260
+ className="me-1",
261
+ ),
262
+ width={"offset": 1, "size": 3},
263
+ ),
264
+ dbc.Col(
265
+ dbc.Button(
266
+ "Mise à jour OSITAH",
267
+ id=NSIP_SYNC_APPLY_DIFF_ID,
268
+ color="secondary",
269
+ className="me-1",
270
+ disabled=True,
271
+ ),
272
+ width={"offset": 1, "size": 2},
273
+ ),
274
+ ]
275
+ ),
276
+ html.P(),
277
+ html.Div(id=NSIP_SYNC_CONTENT_ID),
278
+ ]
279
+ )
280
+
281
+
282
+ def project_teams_layout():
283
+ """
284
+ Define the layout specific to the configuration of project teams
285
+
286
+ :return: html.Div component
287
+ """
288
+
289
+ activities = get_all_hito_activities(True)
290
+ masterproject_items = []
291
+ for masterproject in sorted(activities.masterproject.unique(), key=lambda x: x.upper()):
292
+ if masterproject != MASTERPROJECT_DELETED_ACTIVITY:
293
+ masterproject_items.append({"label": masterproject, "value": masterproject})
294
+
295
+ return html.Div(
296
+ [
297
+ html.P(),
298
+ dbc.Alert(id=ACTIVITY_TEAMS_STATUS_ID, is_open=False),
299
+ dbc.Row(
300
+ [
301
+ dbc.Col(
302
+ [
303
+ dbc.Label(html.B("Masterprojet")),
304
+ dbc.Select(
305
+ id=ACTIVITY_TEAMS_MASTERPROJECTS_ID,
306
+ options=masterproject_items,
307
+ html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
308
+ ),
309
+ ],
310
+ width={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH, "offset": 0},
311
+ xxl={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL, "offset": 0},
312
+ ),
313
+ dbc.Col(
314
+ [
315
+ dbc.Label(html.B("Projet")),
316
+ dbc.Select(
317
+ id=ACTIVITY_TEAMS_PROJECTS_ID,
318
+ html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
319
+ ),
320
+ ],
321
+ width={
322
+ "size": ACTIVITY_TEAMS_LIST_BOX_WIDTH,
323
+ "offset": ACTIVITY_TEAMS_LIST_BOX_INTERVAL,
324
+ },
325
+ xxl={
326
+ "size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL,
327
+ "offset": ACTIVITY_TEAMS_LIST_BOX_INTERVAL,
328
+ },
329
+ ),
330
+ ],
331
+ justify="start",
332
+ ),
333
+ dbc.Row(
334
+ [
335
+ dbc.Col(
336
+ [
337
+ dbc.Label(html.B("Equipes IJCLab")),
338
+ dbc.Select(
339
+ id=ACTIVITY_TEAMS_LAB_TEAMS_ID,
340
+ html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
341
+ ),
342
+ ],
343
+ width={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH, "offset": 0},
344
+ xxl={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL, "offset": 0},
345
+ ),
346
+ dbc.Col(
347
+ html.Div(
348
+ [
349
+ dbc.ButtonGroup(
350
+ [
351
+ dbc.Button(
352
+ html.I(className="bi bi-chevron-double-right"),
353
+ id=ACTIVITY_TEAMS_BUTTON_ADD_ID,
354
+ disabled=True,
355
+ style={"marginTop": "1em"},
356
+ ),
357
+ dbc.Button(
358
+ html.I(className="bi bi-chevron-double-left"),
359
+ id=ACTIVITY_TEAMS_BUTTON_REMOVE_ID,
360
+ disabled=True,
361
+ style={"marginTop": "2em"},
362
+ ),
363
+ ],
364
+ vertical=True,
365
+ ),
366
+ ],
367
+ style={"display": "flex", "justifyContent": "center"},
368
+ ),
369
+ width=ACTIVITY_TEAMS_LIST_BOX_INTERVAL,
370
+ align="center",
371
+ ),
372
+ dbc.Col(
373
+ [
374
+ dbc.Label(html.B("Equipes du projet")),
375
+ dbc.Select(
376
+ id=ACTIVITY_TEAMS_SELECTED_TEAMS_ID,
377
+ html_size=ACTIVITY_TEAMS_LIST_MAX_SIZE,
378
+ ),
379
+ ],
380
+ width={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH, "offset": 0},
381
+ xxl={"size": ACTIVITY_TEAMS_LIST_BOX_WIDTH_XXL, "offset": 0},
382
+ ),
383
+ ],
384
+ justify="start",
385
+ style={"marginTop": "5em"},
386
+ ),
387
+ dbc.Row(
388
+ [
389
+ dbc.Col(
390
+ dbc.Button(
391
+ "Mettre à jour",
392
+ id=ACTIVITY_TEAMS_BUTTON_UPDATE_ID,
393
+ disabled=True,
394
+ ),
395
+ width={"size": 3, "offset": 0},
396
+ ),
397
+ dbc.Col(
398
+ dbc.Button(
399
+ "Annuler",
400
+ id=ACTIVITY_TEAMS_BUTTON_CANCEL_ID,
401
+ disabled=True,
402
+ ),
403
+ width={"size": 3, "offset": 0},
404
+ ),
405
+ ],
406
+ justify="evenly",
407
+ style={"marginTop": "3em"},
408
+ ),
409
+ dcc.Store(id=ACTIVITY_TEAMS_PROJECT_ACTIVITY_ID, data=True),
410
+ dcc.Store(id=ACTIVITY_TEAMS_ADDED_TEAMS_ID, data=[]),
411
+ dcc.Store(id=ACTIVITY_TEAMS_REMOVED_TEAMS_ID, data=[]),
412
+ dcc.Store(id=ACTIVITY_TEAMS_RESET_INDICATOR_ID, data=0),
413
+ ],
414
+ )
415
+
416
+
417
+ def project_mgt_layout():
418
+ """
419
+ Build the layout for the project management tab.
420
+
421
+ :return: html.Div component
422
+ """
423
+
424
+ masterproject_list = []
425
+ project_list = []
426
+ return html.Div(
427
+ [
428
+ html.P(),
429
+ dbc.Alert(id=PROJECT_MGT_STATUS_ID, is_open=False),
430
+ dbc.Row(
431
+ [
432
+ dbc.RadioItems(
433
+ id=PROJECT_MGT_PROJECT_TYPE_ID,
434
+ options=[
435
+ {
436
+ "label": "Projets NSIP",
437
+ "value": PROJECT_MGT_PROJECT_TYPE_NSIP,
438
+ },
439
+ {
440
+ "label": "Projets locaux",
441
+ "value": PROJECT_MGT_PROJECT_TYPE_LOCAL,
442
+ },
443
+ {
444
+ "label": "Projets désactivés",
445
+ "value": PROJECT_MGT_PROJECT_TYPE_DISABLED,
446
+ },
447
+ ],
448
+ inline=True,
449
+ ),
450
+ ],
451
+ justify="center",
452
+ style={"marginTop": "1em"},
453
+ ),
454
+ dbc.Row(
455
+ [
456
+ dbc.Col(
457
+ [
458
+ dbc.Label(html.B("Masterprojets")),
459
+ dbc.Select(
460
+ id=PROJECT_MGT_MASTERPROJECT_LIST_ID,
461
+ options=masterproject_list,
462
+ html_size=PROJECT_MGT_LIST_MAX_SIZE,
463
+ ),
464
+ ],
465
+ width={"size": PROJECT_MGT_LIST_BOX_WIDTH, "offset": 0},
466
+ xxl={"size": PROJECT_MGT_LIST_BOX_WIDTH_XXL, "offset": 0},
467
+ ),
468
+ dbc.Col(
469
+ [
470
+ dbc.Label(html.B("Projets")),
471
+ dbc.Select(
472
+ id=PROJECT_MGT_PROJECT_LIST_ID,
473
+ options=project_list,
474
+ html_size=PROJECT_MGT_LIST_MAX_SIZE,
475
+ ),
476
+ ],
477
+ width={"size": PROJECT_MGT_LIST_BOX_WIDTH, "offset": 0},
478
+ xxl={"size": PROJECT_MGT_LIST_BOX_WIDTH_XXL, "offset": 0},
479
+ ),
480
+ ],
481
+ style={"marginTop": "1em"},
482
+ ),
483
+ dbc.Row(
484
+ dbc.Col(
485
+ dbc.Button(
486
+ "",
487
+ id=PROJECT_MGT_ACTION_BUTTON_ID,
488
+ disabled=True,
489
+ style={"visibility": "hidden"},
490
+ ),
491
+ ),
492
+ style={"marginTop": "3em"},
493
+ ),
494
+ dcc.Store(id=PROJECT_MGT_PROJECT_ACTIVITY_ID, data=True),
495
+ ],
496
+ )
497
+
498
+
499
+ def configuration_submenus():
500
+ """
501
+ Build the tabs menus of the configuration subapplication
502
+
503
+ :return: DBC Tabs
504
+ """
505
+
506
+ global_params = GlobalParams()
507
+ if AUTHORIZED_ROLES.index(global_params.session_data.role) > AUTHORIZED_ROLES.index(
508
+ HITO_ROLE_PROJECT_MGR
509
+ ):
510
+ nsip_sync_disabled = True
511
+ declaration_period_disabled = True
512
+ project_mgt_disabled = True
513
+ else:
514
+ nsip_sync_disabled = False
515
+ declaration_period_disabled = False
516
+ project_mgt_disabled = False
517
+
518
+ return dbc.Tabs(
519
+ [
520
+ dbc.Tab(
521
+ id=TAB_ID_ACTIVITY_TEAMS,
522
+ tab_id=TAB_ID_ACTIVITY_TEAMS,
523
+ label="Equipes des projets",
524
+ ),
525
+ dbc.Tab(
526
+ id=TAB_ID_NSIP_PROJECT_SYNC,
527
+ tab_id=TAB_ID_NSIP_PROJECT_SYNC,
528
+ label="Synchronisation NSIP",
529
+ disabled=nsip_sync_disabled,
530
+ ),
531
+ dbc.Tab(
532
+ id=TAB_ID_DECLARATION_PERIODS,
533
+ tab_id=TAB_ID_DECLARATION_PERIODS,
534
+ label="Périodes de déclaration",
535
+ disabled=declaration_period_disabled,
536
+ ),
537
+ dbc.Tab(
538
+ id=TAB_ID_PROJECT_MGT,
539
+ tab_id=TAB_ID_PROJECT_MGT,
540
+ label="Projets",
541
+ disabled=project_mgt_disabled,
542
+ ),
543
+ ],
544
+ id=CONFIGURATION_TAB_MENU_ID,
545
+ persistence=True,
546
+ )