90 Commits

Author SHA1 Message Date
48ad8c772c Feat/responsivite: Prise en compte de la page de gestion de projet
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-10-20 11:41:29 +00:00
42a5db25d6 Feat/responsivite: Ajout de la page tableau de bord oublié dans l'adaptation précédente
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-10-20 10:54:23 +00:00
eea085af2e Feat\Responsivite
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-10-19 12:16:26 +00:00
Soriba SYLLA
fd5b3b86f7 Synchronisation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-10-13 11:21:55 +00:00
Soriba SYLLA
0abbb26149 Fix/Correction affectation departement
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-10-07 10:30:08 +00:00
f061f482d9 Feat: Hierarchisation des validations des demandes de reservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-15 12:46:19 +00:00
622b412a21 Fix: Permettre à un utilisateur d'annuler sa reservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-14 11:17:01 +00:00
54dc65f0d8 Feat: Envoie de mail à l'équipe informatique après reservation de salle necessitant un lien zoom ou ordinateur
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-14 10:42:12 +00:00
Fatimac2804
29a42ee6be feat:Ajout notification email équipe informatique
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-09-10 12:23:45 +02:00
904954c735 Fix: Modification de mot de passe
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-08 11:09:53 +00:00
99e764ab4d Merge branch 'main' of https://jenkins.cerfig.org/CERFIG/sirh 2026-09-07 12:10:34 +00:00
0eb745d5f3 Fix: Correction de l'affichage des dates dans les détails de la réservation 2026-09-07 12:07:58 +00:00
097648c03e Fix: Correction de l'affichage des dates dans les détails de la réservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-07 11:33:21 +00:00
1a21998c39 Ajout de la barre de navigation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-04 12:19:27 +00:00
900343a4f8 Merge branch 'main' of https://jenkins.cerfig.org/CERFIG/sirh 2026-09-03 14:34:09 +00:00
3c5c58b5d1 Mise a jour: Integration de toutes les suggestions passées 2026-09-03 14:08:01 +00:00
Fatimac2804
3b587d2a1a Affichage des detait de la reservation de salle
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-02 13:46:07 +02:00
Fatimac2804
e3f0befff6 Correction sur l'afficha de la liste de departement pour la modification des informations de l'employer
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-02 12:48:35 +02:00
Soriba SYLLA
55e5a2819c Ajout du contexte processor
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-08-31 09:48:21 +00:00
Soriba SYLLA
7e28b25497 Merge branch 'staging' 2026-08-23 15:14:24 +00:00
643205a4bb Premiere implementation de sso
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-08-04 09:34:31 +00:00
4934b42ed9 Implementation de sso 2026-07-29 10:26:22 +00:00
f9ca539670 Supprimer gestion_salle/migrations/0001_initial.py
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-07-14 09:17:13 +00:00
7b4778252f Supprimer gestion_projet/migrations/0001_initial.py
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:17:02 +00:00
2c086187e7 Supprimer gestion_employe/migrations/0001_initial.py
Some checks are pending
Organisation/sirh/pipeline/head Build queued...
2026-07-14 09:16:51 +00:00
5aff49600e Supprimer gestion_conge/migrations/0001_initial.py
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:16:28 +00:00
8c6e59e15a Correction enregistrement profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:03:28 +00:00
415860f233 Filtrage des reservations par salle
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-15 17:13:02 +00:00
8503689aa4 Bug: Correction du refus des reservations et d'envoi de mail
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-15 16:31:11 +00:00
b03a4214c0 Bug: Validation des conges
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-11 15:33:04 +00:00
d4f4b7d954 Bug: Validation des conges
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-11 15:27:50 +00:00
489eeb439f Correction Bug : Validation 2026-05-11 14:46:59 +00:00
5277b7f355 Bug: Validation de reservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-11 13:20:13 +00:00
9d9e6c6549 Bug: Affichage de la lite des conges en fonction des profil
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-07 15:32:22 +00:00
784822478e feature: Affichage de la liste des bailleur 2026-05-07 15:28:52 +00:00
ab87baaa3a Bug: Affichage des details de la reservation 2026-05-07 15:28:42 +00:00
6026af5498 Bug : Affichages des conges 2026-05-07 15:23:47 +00:00
91bdd791f1 Bug : Affichage des demandes de conges 2026-05-07 15:11:25 +00:00
671072864d Bug: Details des reservations
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-07 14:44:16 +00:00
bb93a853db Gestion de l'envoie des mails
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-07 11:25:11 +00:00
99434a21e0 Fonctionnalite: verifier qu'aucun contrat n'est actif avant la creation d'un nouveau pour un employe
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-06 16:01:37 +00:00
f6b90e7dd0 Verification du statut avant la creation d'un contrat 2026-05-06 15:53:02 +00:00
fcfac71026 clean: remove pycache from tracking 2026-05-06 15:44:16 +00:00
34a261a4af Prise en charge de la photo de profil
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-06 12:25:38 +00:00
f0e8b025a4 Prise en charge de la photo de profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-06 11:51:55 +00:00
62b2938c71 Prise en charge de la photo de profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-06 11:43:54 +00:00
06125b0900 Prise en charge de la photo de profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-06 11:31:49 +00:00
0047b1f91c Ajout de la prise en charge des photos de profil 2026-05-06 08:50:49 +00:00
7ee14e7b3f clean: remove pycache from tracking 2026-05-06 08:42:28 +00:00
d12b014b20 feature: ajout photo de profil 2026-05-05 15:57:35 +00:00
d9b45ac364 Merge pull request 'Finalisation du Jenkinsfile' (#37) from finalisation into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #37
2026-05-04 13:45:46 +00:00
44faeac222 Finalisation du Jenkinsfile
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 13:44:41 +00:00
e1436e8f4e Merge pull request 'Deploiement automatique' (#36) from env2 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #36
2026-05-04 13:16:38 +00:00
4ee7dd9fc7 Deploiement automatique
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 13:15:38 +00:00
a9e708c778 Merge pull request 'Ajout de dependance' (#35) from env2 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #35
2026-05-04 13:04:03 +00:00
e427561cc1 Ajout de dependance
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 13:01:17 +00:00
78fafebc4d Merge pull request 'env1' (#34) from env1 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #34
2026-05-04 12:39:23 +00:00
ecdaa9f9f9 Config des environnement
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 12:38:33 +00:00
9951719b26 Config env 2026-05-04 12:22:15 +00:00
1391a5ea3b Derniere modification du Jenkinsfile
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-05-04 09:58:34 +00:00
357b50dfa4 Merge pull request 'utilisation des credentials' (#32) from pipeline10 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #32
2026-05-04 09:52:26 +00:00
bda80fae85 utilisation des credentials
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 09:51:25 +00:00
9c1eb543ff Merge pull request 'pipeline9' (#31) from pipeline9 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #31
2026-04-30 16:07:01 +00:00
f55b1a6f2d pipeline9
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head This commit looks good
2026-04-30 16:06:21 +00:00
a6412341cf Merge pull request 'pipeline 8' (#30) from pipeline8 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #30
2026-04-30 15:59:17 +00:00
ab2d8b479c pipeline 8
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-04-30 15:57:54 +00:00
8f412cb031 Merge pull request 'pipeline 7' (#29) from pipeline7 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #29
2026-04-30 15:44:04 +00:00
69764b74a2 pipeline 7
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head Build started...
2026-04-30 15:43:00 +00:00
e1dc510af1 Merge pull request 'pipeline 6' (#28) from pipeline6 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #28
2026-04-30 15:28:19 +00:00
fa21850c6b pipeline 6
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-04-30 15:27:29 +00:00
4a8cbf02b9 Merge pull request 'pipeline 5' (#27) from pipeline5 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #27
2026-04-30 15:21:29 +00:00
798784a163 pipeline 5
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head This commit looks good
2026-04-30 15:21:02 +00:00
86ad99de76 Merge pull request 'pipeline 4' (#25) from pipeline4 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #25
2026-04-30 15:09:54 +00:00
5680677865 pipeline 4
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head Build started...
2026-04-30 15:09:22 +00:00
886c3246af Merge pull request 'pipeline3' (#24) from pipeline3 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #24
2026-04-30 15:03:16 +00:00
7b66250832 pipeline3
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-04-30 15:01:54 +00:00
80b3573674 Merge pull request 'pipeline 2' (#23) from pipeline2 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #23
2026-04-30 14:56:41 +00:00
efadd66483 pipeline 2
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head Build started...
2026-04-30 14:56:06 +00:00
c9431063de Merge pull request 'Pipeline 1' (#22) from pipeline1 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #22
2026-04-30 14:51:42 +00:00
15c33efc14 Pipeline 1
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main Build queued...
2026-04-30 14:50:58 +00:00
5257901e75 Merge pull request 'Test pipeline' (#21) from jenkins_agent_exemple into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #21
2026-04-30 14:47:05 +00:00
b3294a823e Test pipeline
Some checks failed
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-04-30 14:45:35 +00:00
2a182830a6 Merge pull request 'Ajout de l'agent' (#20) from jenkins_agent into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #20
2026-04-30 13:18:30 +00:00
14631c3744 Ajout de l'agent
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head This commit looks good
2026-04-30 13:17:43 +00:00
f0894bb66f Merge pull request 'jenkins test 3' (#18) from jenkins into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #18
2026-04-30 13:12:06 +00:00
75285a140a jenkins test 3
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-04-30 13:11:14 +00:00
3cc8e292b3 Merge pull request 'renommage jenkinsfile' (#16) from test_jenkins into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #16
2026-04-30 13:02:50 +00:00
34d1464391 renommage jenkinsfile
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-04-30 13:00:26 +00:00
6c12131fab Merge pull request 'Test jenkins 1' (#15) from test_jenkins into main
Reviewed-on: #15
2026-04-30 12:48:13 +00:00
19e4675f32 Test jenkins 1 2026-04-30 12:47:18 +00:00
91 changed files with 872 additions and 3204 deletions

3
Jenkinsfile vendored
View File

@@ -2,7 +2,6 @@ pipeline
{ {
agent any agent any
options { options {
// This is required if you want to clean before build
skipDefaultCheckout(true) skipDefaultCheckout(true)
} }
@@ -17,7 +16,7 @@ pipeline
{ {
steps steps
{ {
sh 'echo "Debut du pipeline"' sh 'echo "Debut du pipeline test"'
cleanWs() cleanWs()
checkout scm checkout scm
} }

Binary file not shown.

View File

@@ -1,9 +1,8 @@
# SGL/sso.py # SGL/sso.py
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from simple_sso.sso_client.client import Client
from gestion_employe.models import Departement, Employe from gestion_employe.models import Departement, Employe
from simple_sso.sso_client.client import Client
User = get_user_model() User = get_user_model()
@@ -12,8 +11,6 @@ class SIRHClient(Client):
"""Crée le User et son Profile avec les infos reçues du serveur.""" """Crée le User et son Profile avec les infos reçues du serveur."""
user_data_copy = user_data.copy() user_data_copy = user_data.copy()
print("Un petit test", user_data_copy)
user_data.pop("departement", None) user_data.pop("departement", None)
user_data.pop("chef_departement", None) user_data.pop("chef_departement", None)
user_data.pop("groups", None) user_data.pop("groups", None)

View File

@@ -14,10 +14,10 @@
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
<div class="row flex-nowrap">
{% include 'parts/menu_application.html' %} {% include 'parts/menu_application.html' %}
<div class="row flex-nowrap">
{% include 'parts/menu_principal.html' %} {% include 'parts/menu_principal.html' %}
<div class="col p-4"> <div class="col p-1 p-sm-4">
{% block 'contenu' %} {% endblock %} {% block 'contenu' %} {% endblock %}
</div> </div>
</div> </div>

View File

@@ -1,8 +1,99 @@
<div class="col-1 bg-danger border d-flex flex-column vh-100 pt-5 sticky-top"> {% load static %}
<a href="{{ SIRH_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em"> {% load tags_personnaliser %}
<i class="bi bi-people-fill fs-1 d-block"></i>SIRH
<nav class="navbar d-none d-lg-block navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<img src="{% static 'img/cerfig.jpg' %}" alt="Logo" width="85" height="30" class="d-inline-block align-text-top">
</a> </a>
<a href="{{ SGL_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <div class="collapse navbar-collapse">
<i class="bi bi-building-gear fs-1 d-block"></i> SGL <ul class="nav nav-tabs justify-content-end w-100">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ SIRH_LIEN }}">SIRH</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ SGL_LIEN }}">SGL</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Laboratoire</a>
</li>
</ul>
</div>
</div>
</nav>
<nav class="navbar d-lg-none navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<img src="{% static 'img/cerfig.jpg' %}" alt="Logo" width="85" height="30" class="d-inline-block align-text-top">
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="{% url 'gestion_employe:mon-profil' %}">
<i class="bi bi-person-circle"></i>
Mon profil
</a>
</li>
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
<li class="nav-item">
<a class="nav-link" aria-current="page" href="{% url 'gestion_employe:index' %}">
<i class="bi bi-speedometer2"></i>
Tableau de bord
</a>
</li>
{% endif %}
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
<li class="nav-item">
<a class="nav-link" aria-current="page" href="{% url 'gestion_projet:index' %}">
<i class="bi bi-speedometer2"></i>
Gestion des projets
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{% url 'gestion_conges:conge' %}">
<i class="bi bi-airplane"></i>
Gestion des congés
</a>
</li>
{% if user|is_chef_projet %}
<li class="nav-item">
<a class="nav-link" href="{% url 'gestion_projet:activites-projet' %}">
<i class="bi bi-list-task"></i>
Suivi des Activités
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{% url 'gestion_salle:reservation-salle' %}">
<i class="bi bi-calendar-check"></i>
Réservation
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'deconnexion' %}">
<i class="bi bi-box-arrow-right"></i> Déconnexion
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid d-flex d-sm-none justify-content-center">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ SIRH_LIEN }}">SIRH</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ SGL_LIEN }}">SGL</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Laboratoire</a>
</li>
</ul>
</div> </div>

View File

@@ -1,7 +1,7 @@
{% load static %} {% load static %}
{% load tags_personnaliser %} {% load tags_personnaliser %}
<div class="col-3 bg-danger d-flex flex-column vh-100 pt-5 sticky-top"> <div class="col-3 bg-danger d-none d-sm-flex flex-column vh-100 pt-5 sticky-top">
<div class="text-center mb-4"> <div class="text-center mb-4">
{% if user.employe.photo %} {% if user.employe.photo %}
<img src="{{ user.employe.photo.url }}" <img src="{{ user.employe.photo.url }}"
@@ -41,7 +41,6 @@
<a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;"> <a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-calendar-check"></i> Réservation <i class="bi bi-calendar-check"></i> Réservation
</a> </a>
{% comment %} {% url 'deconnexion' %} {% endcomment %}
<a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;"> <a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-box-arrow-right"></i> Déconnexion <i class="bi bi-box-arrow-right"></i> Déconnexion
</a> </a>

View File

@@ -38,7 +38,7 @@ urlpatterns = [
name='deconnexion' name='deconnexion'
), ),
path( path(
'gestion-employe/', '',
include("gestion_employe.urls") include("gestion_employe.urls")
), ),
path( path(

View File

@@ -1,3 +1,5 @@
from django.conf import settings
from django.core.mail import send_mail
from django.utils import timezone from django.utils import timezone
from gestion_conge.models import Conge from gestion_conge.models import Conge
@@ -7,13 +9,27 @@ QUOTA_CONGE_ANNUEL = 30
NOMBRE_PAGINATION = 8 NOMBRE_PAGINATION = 8
DEBUT_RAPPEL = 60 DEBUT_RAPPEL = 60
DUREE_FIN_CONTRAT = 90 DUREE_FIN_CONTRAT = 90
EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True))
try: try:
EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True)) EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True))
except: except:
pass pass
try:
EMAIL_EQUIPE_INFORMATIQUE = list(
Employe.objects.filter(departement__nom="Systeme informatique").values_list('user__email', flat=True)
) or ["support@cerfig.org"]
except:
EMAIL_EQUIPE_INFORMATIQUE = ["support@cerfig.org"]
def est_equipe_informatique(employe):
"""Vérifie si un employé appartient au département informatique"""
return employe.departement and employe.departement.nom == "Systeme informatique"
def est_assistante_direction(employe):
"""Vérifie si un employé est l'assistante de direction"""
return employe.fonction == "assistant_direction"
def solde_conge(employe): def solde_conge(employe):
"""Fonction de calcul du solde de congé restant l'employé""" """Fonction de calcul du solde de congé restant l'employé"""
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first() contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()
@@ -47,3 +63,14 @@ def destinataire_mail_demande_conges(employe):
if employe.departement: if employe.departement:
chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True) chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True)
return list(chefs_departement.values_list('user__email', flat=True)) return list(chefs_departement.values_list('user__email', flat=True))
def envoyer_mail(sujet, message, destinataires):
"""Fonction d'envoi de mail"""
send_mail(
sujet,
message,
settings.EMAIL_HOST_USER,
destinataires,
fail_silently=False,
)

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD # Generated by Django 5.2.13 on 2026-09-03 14:33
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View File

@@ -7,7 +7,8 @@
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div> <div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div class="row d-flex justify-content-center mb-4">
<div class="row d-flex flex-column flex-lg-row justify-content-center mb-4">
<div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4"> <div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4">
<div class="card-header fw-bold"> <div class="card-header fw-bold">
<i class="bi bi-x-circle me-2"></i> Congés refusés <i class="bi bi-x-circle me-2"></i> Congés refusés
@@ -16,7 +17,7 @@
<h5 class="fw-bold">{{ nombre_conges_refuse }}</h5> <h5 class="fw-bold">{{ nombre_conges_refuse }}</h5>
</div> </div>
</div> </div>
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2"> <div class="col my-2 my-lg-0 text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-0 mx-lg-2">
<div class="card-header fw-bold"> <div class="card-header fw-bold">
<i class="bi bi-hourglass-split me-2"></i> Congés en attente <i class="bi bi-hourglass-split me-2"></i> Congés en attente
</div> </div>
@@ -35,7 +36,7 @@
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col d-flex justify-content-between align-items-center"> <div class="col d-flex flex-column flex-lg-row justify-content-between align-items-center">
<h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3> <h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3>
<button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button> <button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button>
</div> </div>

View File

@@ -129,7 +129,7 @@ def demander_conge(request):
fonctions_utilitaire.envoyer_mail( fonctions_utilitaire.envoyer_mail(
sujet = "Demande de congé", sujet = "Demande de congé",
message = f"""Bonjour {employe.user.first_name} {employe.user.last_name}, votre demande de congé a été enregistrée. Veuillez consulter votre profil pour plus de détails.""", message = f"""Bonjour {employe.user.first_name} {employe.user.last_name}, votre demande de congé a été enregistrée. Veuillez consulter votre profil pour plus de détails.""",
destinataires = fonctions_utilitaire.destinataire_mail_demande_conges() + [employe.user.email] destinataires = fonctions_utilitaire.destinataire_mail_demande_conges(employe)
) )
return redirect("gestion_conges:conge") return redirect("gestion_conges:conge")

View File

@@ -8,6 +8,8 @@ class EmployeForm(forms.ModelForm):
class Meta: class Meta:
model = Employe model = Employe
fields = ( fields = (
'departement',
'fonction',
'adresse', 'adresse',
'telephone', 'telephone',
'CV', 'CV',
@@ -16,6 +18,17 @@ class EmployeForm(forms.ModelForm):
'photo', 'photo',
'casier_judiciaire' 'casier_judiciaire'
) )
widgets = {
'departement': forms.Select(attrs={'class': 'form-select'}),
'fonction': forms.Select(attrs={'class': 'form-select'}),
'adresse': forms.TextInput(attrs={'class': 'form-control'}),
'telephone': forms.TextInput(attrs={'class': 'form-control'}),
'CV': forms.FileInput(attrs={'class': 'form-control'}),
'diplome': forms.FileInput(attrs={'class': 'form-control'}),
'rib': forms.FileInput(attrs={'class': 'form-control'}),
'photo': forms.FileInput(attrs={'class': 'form-control'}),
'casier_judiciaire': forms.FileInput(attrs={'class': 'form-control'}),
}
class AffectationForm(forms.ModelForm): class AffectationForm(forms.ModelForm):
class Meta: class Meta:
@@ -62,7 +75,7 @@ class ContratForm(forms.ModelForm):
class DepartementForm(forms.ModelForm): class DepartementForm(forms.ModelForm):
class Meta: class Meta:
model = Departement model = Departement
fields = ('nom') fields = ('nom', )
class FormationForm(forms.ModelForm): class FormationForm(forms.ModelForm):
class Meta: class Meta:

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD # Generated by Django 5.2.13 on 2026-09-03 14:33
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings from django.conf import settings
@@ -39,19 +27,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('matricule', models.CharField(blank=True, max_length=10, null=True, unique=True)), ('matricule', models.CharField(blank=True, max_length=10, null=True, unique=True)),
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('assistant_technique_recherche', 'Assistant technique de recherche'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)), ('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('assistant_technique_recherche', 'Assistant technique de recherche'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)),
=======
('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)),
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)),
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)),
>>>>>>> 6026af5 (Bug : Affichages des conges)
('date_embauche', models.DateField(blank=True, null=True)), ('date_embauche', models.DateField(blank=True, null=True)),
('adresse', models.CharField(blank=True, max_length=100, null=True)), ('adresse', models.CharField(blank=True, max_length=100, null=True)),
('telephone', models.CharField(blank=True, max_length=15, null=True)), ('telephone', models.CharField(blank=True, max_length=15, null=True)),
@@ -72,19 +48,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('numero_contrat', models.CharField(max_length=100, unique=True)), ('numero_contrat', models.CharField(max_length=100, unique=True)),
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50)), ('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50)),
=======
('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage')], max_length=50)),
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage')], max_length=50)),
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage')], max_length=50)),
>>>>>>> 6026af5 (Bug : Affichages des conges)
('date_debut', models.DateField()), ('date_debut', models.DateField()),
('date_fin', models.DateField(blank=True, null=True)), ('date_fin', models.DateField(blank=True, null=True)),
('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), ('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),

View File

@@ -1,18 +0,0 @@
# Generated by Django 6.0.4 on 2026-04-29 10:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestion_employe', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='contrat',
name='type_contrat',
field=models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50),
),
]

View File

@@ -9,17 +9,15 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
{"title": "Projet", "field": "projet"}, {"title": "Projet", "field": "projet"},
{"title": "Téléphone", "field": "telephone"}, {"title": "Téléphone", "field": "telephone"},
], ],
// ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10
responsiveLayout : "hide" // responsiveLayout : "hide"
}) })
fetch(url_liste_employe) fetch(url_liste_employe)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success){ if (data.success){
console.log(data.data)
tableau_liste_employe.setData(data.data); tableau_liste_employe.setData(data.data);
}else{ }else{
alert(data.message); alert(data.message);
@@ -29,6 +27,7 @@ fetch(url_liste_employe)
tableau_liste_employe.on("rowClick", function (row, rowData) { tableau_liste_employe.on("rowClick", function (row, rowData) {
const data = rowData.getData(); const data = rowData.getData();
document.getElementById('detail-id').value = data.id; document.getElementById('detail-id').value = data.id;
document.getElementById('detail-matricule').value = data.matricule; document.getElementById('detail-matricule').value = data.matricule;
document.getElementById('detail-employe').value = data.employe; document.getElementById('detail-employe').value = data.employe;
@@ -211,23 +210,31 @@ $("enregistrerDetail").addEventListener("click", function() {
const fonction = document.getElementById('detail-fonction').value; const fonction = document.getElementById('detail-fonction').value;
const dateEmbauche = document.getElementById('detail-dateEmbauche').value; const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
const matricule = document.getElementById('detail-matricule').value; const matricule = document.getElementById('detail-matricule').value;
const departement = document.getElementById('detail-departement').value;
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url;
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset)
const payload = {
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule,
"departement": departement
};
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
fetch(url_enregistrer_detail, { fetch(url_enregistrer_detail, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value "X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
}, },
body: JSON.stringify({ body: JSON.stringify(payload)
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule
})
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
console.log('Réponse du serveur:', data);
alert(data.message); alert(data.message);
location.reload(); location.reload();
}) })
@@ -263,15 +270,12 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants, ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
}) })
let tabContrats = null; /* let tabContrats = null;
document.getElementById("modalContratsEmploye") document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () {
.addEventListener("show.bs.modal", function () {
const employeId = document.getElementById("detail-id").value; const employeId = document.getElementById("detail-id").value;
console.log("Employe ID :", employeId);
if (!employeId) { if (!employeId) {
alert("Aucun employé sélectionné"); alert("Aucun employé sélectionné");
return; return;
@@ -330,4 +334,4 @@ document.getElementById("modalContratsEmploye")
.catch(error => { .catch(error => {
console.error("Erreur :", error); console.error("Erreur :", error);
}); });
}); }); */

View File

@@ -13,42 +13,40 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
inputFormat:"yyyy-MM-dd", inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy", outputFormat:"dd/MM/yy",
}}, }},
{"title": "Fichier", "field": "certificat", formatter:"link", formatterParams:{
label:"Voir le fichier",
target:"_blank",
}},
], ],
ajaxURL: url_certificat, ajaxURL: url_certificat,
}) })
const enregistrerProfil = $("enregistrerProfil"); const enregistrerProfil = $("enregistrerProfil");
// enregistrerProfil.addEventListener("click", (e) => { enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
// const url = $("information-personnelles").dataset.url; const formData = new FormData();
// const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value; formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
const photoInput = $("photo");
if (photoInput.files.length > 0) {
formData.append("photo", photoInput.files[0]);
}
// const formData = new FormData(); fetch(url, {
// formData.append("nom", $("nom").value); method: "POST",
// formData.append("prenom", $("prenom").value); headers: {
// formData.append("email", $("email").value); "X-CSRFToken": csrftoken
// formData.append("telephone", $("telephone").value); },
// formData.append("adresse", $("adresse").value); body: formData
// formData.append("sexe", $("sexe").value); })
// formData.append("date_naissance", $("date_naissance").value); .then(response => response.json())
// const photoInput = $("photo"); .then(data => alert(data.message))
// if (photoInput.files.length > 0) { .catch(error => console.error("Erreur:", error));
// formData.append("photo", photoInput.files[0]); });
// }
// console.log("---------------------------------------------")
// console.log("Données du formulaire:", url);
// console.log("Données du formulaire:", formData);
// fetch(url, {
// method: "POST",
// headers: {
// "X-CSRFToken": csrftoken
// },
// body: formData
// })
// .then(response => response.json())
// .then(data => alert(data.message))
// .catch(error => console.error("Erreur:", error));
// });

View File

@@ -15,12 +15,12 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div class="row"> <div class="row d-flex flex-column flex-lg-row">
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4"> <div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Nombre d'employés</span> <span class="fs-5">Nombre d'employés</span>
<h3>{{ nombre_employes }}</h3> <h3>{{ nombre_employes }}</h3>
</div> </div>
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-3"> <div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-0 mx-lg-3 my-1 my-lg-0">
<span class="fs-5">Nombre de prestataires</span> <span class="fs-5">Nombre de prestataires</span>
<h3>{{ nombre_cps }}</h3> <h3>{{ nombre_cps }}</h3>
</div> </div>
@@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<div class="row mt-4"> <div class="row mt-4">
<div class="col d-flex justify-content-between mb-3"> <div class="col d-flex flex-column flex-lg-row justify-content-between mb-3">
<h3> <h3>
<i class="bi bi-people"></i> <i class="bi bi-people"></i>
La liste des employés La liste des employés

View File

@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %} {% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %}
{% block 'contenu' %} {% block 'contenu' %}
<div class="d-flex justify-content-between"> <div class="d-flex flex-column flex-lg-row justify-content-between">
<h5 class="fw-bold"> <h5 class="fw-bold">
<i class="bi bi-pencil-square me-2"></i> Les informations de mon profil <i class="bi bi-pencil-square me-2"></i> Les informations de mon profil
</h5> </h5>
@@ -12,7 +12,7 @@
</div> </div>
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert"> <div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show mt-2" role="alert">
{{ message }} {{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
@@ -41,7 +41,7 @@
<div class="accordion-body"> <div class="accordion-body">
<form class="row" action = "{% url 'gestion_employe:modifier-employe' %}" method = "POST" enctype = "multipart/form-data" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}"> <form class="row" action = "{% url 'gestion_employe:modifier-employe' %}" method = "POST" enctype = "multipart/form-data" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
{% csrf_token %} {% csrf_token %}
<div class="col"> <div class="col-12 col-sm-6">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Photo</label> <label>Photo</label>
{% if employe.photo %} {% if employe.photo %}
@@ -70,7 +70,7 @@
</div> </div>
</div> </div>
<div class="col"> <div class="col-12 col-sm-6">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Date de naissance :</label> <label>Date de naissance :</label>
<input type="date" class="form-control" id="date_naissance" name = 'date_naissance' value="{{ employe.date_naissance|date:'Y-m-d' }}"> <input type="date" class="form-control" id="date_naissance" name = 'date_naissance' value="{{ employe.date_naissance|date:'Y-m-d' }}">
@@ -172,9 +172,9 @@
<form method="POST" action="{% url 'gestion_employe:enregistrement-document-rh' %}" enctype="multipart/form-data"> <form method="POST" action="{% url 'gestion_employe:enregistrement-document-rh' %}" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col"> <div class="col-12 col-sm-6">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>photo</label> <label>Photo</label>
{% if employe.photo %} {% if employe.photo %}
<span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span> <span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span>
{% endif %} {% endif %}
@@ -195,7 +195,7 @@
<input type="file" class="form-control" name="diplome"> <input type="file" class="form-control" name="diplome">
</div> </div>
</div> </div>
<div class="col"> <div class="col-12 col-sm-6">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Casier judiciaire</label> <label>Casier judiciaire</label>
{% if employe.casier_judiciaire %} {% if employe.casier_judiciaire %}

View File

@@ -23,7 +23,12 @@
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Département :</label> <label>Département :</label>
<input type="text" class="form-control" id="detail-departement"> <select class="form-select" id="detail-departement">
<option value="">-- Sélectionner --</option>
{% for dept in departements %}
<option value="{{ dept.id }}">{{ dept.nom }}</option>
{% endfor %}
</select>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Sexe :</label> <label>Sexe :</label>
@@ -93,9 +98,6 @@
</h5> </h5>
{% if user|has_group:"ressource_humaine" %} {% if user|has_group:"ressource_humaine" %}
<div> <div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalContratsEmploye">
Liste contrat
</button>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalContrat{{ item.employe.id }}"> <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalContrat{{ item.employe.id }}">
<i class="bi bi-file-earmark-text"></i> Créer contrat <i class="bi bi-file-earmark-text"></i> Créer contrat
</button> </button>

View File

@@ -6,7 +6,7 @@
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}"> <form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}" id="formulaire_mot_de_passe">
{% csrf_token %} {% csrf_token %}
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Votre mot de passe actuel :</label> <label>Votre mot de passe actuel :</label>

View File

@@ -1,11 +1,14 @@
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
import requests
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from decouple import config
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.db.models import Sum from django.db.models import Sum
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from django.http import JsonResponse from django.http import JsonResponse
@@ -15,7 +18,7 @@ from django.utils import timezone
from fonction_utilitaire import fonctions_utilitaire from fonction_utilitaire import fonctions_utilitaire
from .forms import AffectationForm, ContratForm, FormationForm from .forms import AffectationForm, ContratForm, FormationForm
from .models import Affectation, Contrat, Employe, Formation from .models import Affectation, Contrat, Departement, Employe, Formation
@login_required @login_required
@@ -24,6 +27,18 @@ def index(request):
if not request.user.groups.filter(name="ressource_humaine").exists(): if not request.user.groups.filter(name="ressource_humaine").exists():
return redirect('gestion_employe:mon-profil') return redirect('gestion_employe:mon-profil')
response = requests.get(f"{config('SERVEUR_AUTHENTIFICATION')}recuperer-list-departement/")
if response.status_code == 200:
try:
departements_data = response.json()
for dept in departements_data:
Departement.objects.update_or_create(
nom = dept['nom']
)
except json.JSONDecodeError as e:
print(f"[ERROR] Erreur lors du décodage JSON : {e}")
employes = Employe.objects.all().order_by('-user__date_joined') employes = Employe.objects.all().order_by('-user__date_joined')
nombre_employes = Employe.objects.count() nombre_employes = Employe.objects.count()
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count() nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
@@ -42,7 +57,8 @@ def index(request):
'nombre_stage': nombre_stage, 'nombre_stage': nombre_stage,
'nombre_expirants': nombre_expirants, 'nombre_expirants': nombre_expirants,
'affectation_form': AffectationForm(), 'affectation_form': AffectationForm(),
'contrat_form': ContratForm() 'contrat_form': ContratForm(),
'departements': Departement.objects.all()
}) })
@login_required @login_required
@@ -146,15 +162,6 @@ def mon_profil(request):
date_fin_daffectation__gte=timezone.now().date() date_fin_daffectation__gte=timezone.now().date()
).select_related('projet') ).select_related('projet')
has_contrat = contrats is not None
expiration_contrat = False
contrat_nb_jours_restant = None
if contrats:
nb_jours = contrats.nombre_jours_restant
contrat_nb_jours_restant = nb_jours
expiration_contrat = nb_jours <= fonctions_utilitaire.DUREE_FIN_CONTRAT
return render( return render(
request, request,
'gestion_employe/monprofil.html', 'gestion_employe/monprofil.html',
@@ -179,30 +186,11 @@ def mon_profil(request):
], ],
"formation_form": FormationForm(), "formation_form": FormationForm(),
"has_contrat": has_contrat, "has_contrat": contrats is not None,
"expiration_contrat": expiration_contrat, "expiration_contrat": contrats.nombre_jours_restant <= fonctions_utilitaire.DUREE_FIN_CONTRAT if contrats else False,
"contrat_nb_jours_restant": contrat_nb_jours_restant "contrat_nb_jours_restant": max(0, contrats.nombre_jours_restant if contrats else 0)
} }
) )
@login_required
def modifier_mot_passe(request):
"""Vue pour permettre à un utilisateur de modifier son mot de passe et ses informations de profil"""
user = User.objects.get(username=request.user)
if request.method == "POST":
ancien_mdp = request.POST["ancien-mdp"]
nouveau_mdp = request.POST["nouveau-mdp"]
confirmation_mdp = request.POST["confirmation-mdp"]
if authenticate(request, username=request.user, password=ancien_mdp) is None:
messages.error(request, "Ancien mot de passe incorrect.")
elif nouveau_mdp != confirmation_mdp:
messages.error(request, "Les deux nouveaux ne correspondent pas.")
else:
user.set_password(nouveau_mdp)
user.save()
messages.success(request, "Mot de passe modifié avec succès.")
return redirect("gestion_employe:mon-profil")
def modifier_employer(request): def modifier_employer(request):
"""Vue pour permettre à un utilisateur de modifier les informations d'un employé""" """Vue pour permettre à un utilisateur de modifier les informations d'un employé"""
@@ -231,7 +219,6 @@ def modifier_employer(request):
messages.success(request, "Profil mis à jour avec succès.") messages.success(request, "Profil mis à jour avec succès.")
return redirect("gestion_employe:mon-profil") return redirect("gestion_employe:mon-profil")
def enregistrement_document(request): def enregistrement_document(request):
"""Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil""" """Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil"""
employe = Employe.objects.get(user=request.user) employe = Employe.objects.get(user=request.user)
@@ -285,7 +272,7 @@ def creation_contrat(request):
contrat_actif = Contrat.objects.filter( contrat_actif = Contrat.objects.filter(
employe=employe, employe=employe,
date_fin__gte=datetime.date.today() date_fin__gte=timezone.now().date()
).exists() ).exists()
if request.method == "POST": if request.method == "POST":
@@ -311,7 +298,6 @@ def creation_contrat(request):
'contrat_form': form 'contrat_form': form
}) })
@login_required @login_required
def contrats_employe(request, employe_id): def contrats_employe(request, employe_id):
@@ -350,6 +336,7 @@ def contrats_employe(request, employe_id):
"employe": employe.user.get_full_name(), "employe": employe.user.get_full_name(),
"contrats": data "contrats": data
}) })
@login_required @login_required
def enregistrer_detail_employe(request): def enregistrer_detail_employe(request):
"""Vue pour permettre à un utilisateur de modifier les détails d'un employé via une requête AJAX""" """Vue pour permettre à un utilisateur de modifier les détails d'un employé via une requête AJAX"""
@@ -360,15 +347,31 @@ def enregistrer_detail_employe(request):
except Employe.DoesNotExist: except Employe.DoesNotExist:
return JsonResponse({"error": "Employé non trouvé."}, status=404) return JsonResponse({"error": "Employé non trouvé."}, status=404)
employe.fonction = data['fonction'] employe.fonction = data.get('fonction')
employe.date_embauche = data['date_embauche'] employe.date_embauche = data.get('date_embauche')
employe.matricule = data['matricule'] employe.matricule = data.get('matricule')
# Traiter le département
dept_value = data.get('departement')
if dept_value and str(dept_value).strip():
try:
dept_id = int(dept_value)
departement = Departement.objects.get(id=dept_id)
employe.departement = departement
except Departement.DoesNotExist:
employe.departement = None
except (ValueError, TypeError) as e:
employe.departement = None
else:
employe.departement = None
employe.save() employe.save()
return JsonResponse({"message": "Détails de l'employé mis à jour avec succès."}) return JsonResponse({"message": "Détails de l'employé mis à jour avec succès."})
else: else:
return JsonResponse({"message": "Méthode non autorisée."}, status=405) return JsonResponse({"message": "Méthode non autorisée."}, status=405)
@login_required @login_required
def liste_employe(request): def liste_employe(request):
""" Vue pour retourner la liste de tous les employés """ """ Vue pour retourner la liste de tous les employés """
@@ -380,8 +383,8 @@ def liste_employe(request):
projets = [ projets = [
", ".join([ ", ".join([
a.projet.nom_projet for a in Affectation.objects.filter( a.projet.nom_projet for a in Affectation.objects.filter(
employe=employe, employe=employe
date_fin_daffectation__gte=timezone.now().date() #, date_fin_daffectation__gte=timezone.now().date()
) )
]) ])
] ]
@@ -405,14 +408,14 @@ def liste_employe(request):
"salaire_mensuel": contrat.salaire_mensuel, "salaire_mensuel": contrat.salaire_mensuel,
"statut": contrat.statut, "statut": contrat.statut,
"fichier_contrat": contrat.fichier_contrat.url if contrat.fichier_contrat else "", "fichier_contrat": contrat.fichier_contrat.url if contrat.fichier_contrat else "",
} for contrat in Contrat.objects.filter(employe=employe, statut='actif') } for contrat in Contrat.objects.filter(employe=employe)#, statut='actif')
] ]
affectations = [ affectations = [
{**model_to_dict(affectation), "projet": affectation.projet.nom_projet} {**model_to_dict(affectation), "projet": affectation.projet.nom_projet}
for affectation in Affectation.objects.filter( for affectation in Affectation.objects.filter(
employe=employe, employe=employe
date_fin_daffectation__gte=timezone.now().date() #, date_fin_daffectation__gte=timezone.now().date()
) )
] ]
@@ -426,7 +429,7 @@ def liste_employe(request):
"affectations": affectations, "affectations": affectations,
"projet": projets, "projet": projets,
"contrats": contrats, "contrats": contrats,
"departement": employe.departement.nom if employe.departement else "", "departement": employe.departement.id if employe.departement else "",
"fonction": employe.fonction, "fonction": employe.fonction,
"date_embauche": employe.date_embauche, "date_embauche": employe.date_embauche,
"adresse": employe.adresse, "adresse": employe.adresse,
@@ -498,3 +501,47 @@ def supprimer_formation(request, id_formation):
formation.delete() formation.delete()
messages.success(request, "Formation supprimée ") messages.success(request, "Formation supprimée ")
return redirect("mes_formations") return redirect("mes_formations")
@login_required
def modifier_mot_passe(request):
"""Vue pour permettre à un employé de modifier son mot de passe"""
if request.method == "POST":
username = request.user.username
ancien_mdp = request.POST.get("ancien-mdp")
nouveau_mdp = request.POST.get("nouveau-mdp")
confirmation_mdp = request.POST.get("confirmation-mdp")
try:
validate_password(nouveau_mdp, user=request.user)
except ValidationError as e:
messages.error(request, f"Erreur : {' '.join(e.messages)}")
return redirect("gestion_employe:mon-profil")
donnees = {
"username": username,
"ancien_mdp": ancien_mdp,
"nouveau_mdp": nouveau_mdp,
"confirmation_mdp": confirmation_mdp
}
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
response = requests.post(
f"{config('SERVEUR_AUTHENTIFICATION')}modifier-mot-de-passe/",
json=donnees,
headers=headers
)
data = response.json()
cle = next(iter(data.keys()))
valeur = next(iter(data.values()))
if cle == "success":
messages.success(request, valeur)
else:
messages.error(request, valeur)
return redirect("gestion_employe:mon-profil")

View File

@@ -22,7 +22,6 @@ class ProjetForm(forms.ModelForm):
'date_fin', 'date_fin',
'numero_convention', 'numero_convention',
'domaine_recherche', 'domaine_recherche',
'type_projet',
'budget', 'budget',
'budget_RH', 'budget_RH',
'description' 'description'
@@ -37,7 +36,6 @@ class ProjetForm(forms.ModelForm):
'nom_projet': forms.TextInput(attrs={'class': "form-control"}), 'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
'numero_convention': forms.TextInput(attrs={'class': "form-control"}), 'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}), 'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
'type_projet': forms.Select(attrs={'class': "form-select"}),
'budget': forms.NumberInput(attrs={'class': "form-control"}), 'budget': forms.NumberInput(attrs={'class': "form-control"}),
'budget_RH': forms.NumberInput(attrs={'class': "form-control"}), 'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
'description': forms.Textarea(attrs={'class': "form-control"}), 'description': forms.Textarea(attrs={'class': "form-control"}),

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD # Generated by Django 5.2.13 on 2026-09-03 14:33
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View File

@@ -1,5 +1,3 @@
from datetime import datetime
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@@ -33,9 +31,11 @@ class DomaineDeRecherche(models.Model):
DOMAINE_RECHERCHE = ( DOMAINE_RECHERCHE = (
('sciences_sociales', 'Sciences sociales'), ('sciences_sociales', 'Sciences sociales'),
('naturelles', 'Naturelles'),
('humaines', 'Humaines'), ('humaines', 'Humaines'),
('veterinaires', 'Vétérinaires') ('epidemiologie', 'Epidémiologie'),
('sciences_biomedicales', 'Sciences biomédicales'),
('service_pedagogique', 'Service Pédagogique'),
('recherche_clinique', 'Recherche clinique')
) )
nom = models.CharField( nom = models.CharField(
@@ -54,13 +54,6 @@ class DomaineDeRecherche(models.Model):
class Projet(models.Model): class Projet(models.Model):
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé.""" """Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
TYPE_PROJET = (
('laboratoire', 'Laboratoire'),
('épidémiologie', 'Épidémiologie'),
('sciences sociales', 'Sciences sociales'),
('cliniques', 'Cliniques'),
('autre', 'Autre'),
)
id_projet = models.CharField( id_projet = models.CharField(
max_length=100, max_length=100,
@@ -86,12 +79,6 @@ class Projet(models.Model):
description = models.TextField( description = models.TextField(
verbose_name="Description" verbose_name="Description"
) )
type_projet = models.CharField(
max_length=100,
choices=TYPE_PROJET,
default='épidémiologie',
verbose_name="Type de projet"
)
domaine_recherche = models.ManyToManyField(DomaineDeRecherche) domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
budget=models.DecimalField( budget=models.DecimalField(
max_digits=12, max_digits=12,
@@ -114,13 +101,13 @@ class Projet(models.Model):
@property @property
def statut(self): def statut(self):
if self.date_fin < datetime.date.today(): if self.date_fin < timezone.now().date():
return "Terminé" return "Terminé"
return "En cours" return "En cours"
@property @property
def avancement(self): def avancement(self):
aujourd_hui = datetime.date.today() aujourd_hui = timezone.now().date()
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin): if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
duree_projet = (self.date_fin - self.date_debut).days duree_projet = (self.date_fin - self.date_debut).days
temps_ecoule = (aujourd_hui - self.date_debut).days temps_ecoule = (aujourd_hui - self.date_debut).days

View File

@@ -1,5 +1,4 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
let table; let table;
btnEnregistrerBailleur.addEventListener('click', function() { btnEnregistrerBailleur.addEventListener('click', function() {

View File

@@ -1,5 +1,6 @@
const $ = (element) => document.getElementById(element) const $ = (element) => document.getElementById(element)
const url_liste_projet = $("tableau-liste-projet").dataset.url; const url_liste_projet = $("tableau-liste-projet").dataset.url;
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", { const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
fitColumns: true, fitColumns: true,
responsiveLayout : true, responsiveLayout : true,
@@ -39,7 +40,6 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
$("detail_date_debut").value = data.date_debut; $("detail_date_debut").value = data.date_debut;
$("detail_date_fin").value = data.date_fin; $("detail_date_fin").value = data.date_fin;
$("detail_numero_convention").value = data.numero_convention; $("detail_numero_convention").value = data.numero_convention;
$("detail_type_projet").value = data.type_projet;
Array.from($("detail_domaine_recherche").options).forEach(option => { Array.from($("detail_domaine_recherche").options).forEach(option => {
if (data.domaine_recherche.includes(option.value)) { if (data.domaine_recherche.includes(option.value)) {
option.selected = true; option.selected = true;

View File

@@ -12,12 +12,12 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div class="row"> <div class="row d-flex flex-column flex-lg-row">
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4"> <div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Projets en cours</span> <span class="fs-5">Projets en cours</span>
<h3>{{projet_en_cours}}</h3> <h3>{{projet_en_cours}}</h3>
</div> </div>
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2"> <div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4 my-1 my-lg-0 mx-0 mx-lg-2">
<span class="fs-5">Budget Total (GNF)</span> <span class="fs-5">Budget Total (GNF)</span>
<h3>{{budget_total}}</h3> <h3>{{budget_total}}</h3>
</div> </div>
@@ -27,11 +27,11 @@
</div> </div>
</div> </div>
<div class="row mt-4"> <div class="row mt-4">
<div class="col d-flex justify-content-between align-items-center mb-3"> <div class="col d-flex flex-lg-row flex-column justify-content-between align-items-center mb-3">
<h5 class="mb-4" >La liste des projets </h5> <h5 class="mb-4" >La liste des projets </h5>
{% if user|has_group:"ressource_humaine" %} {% if user|has_group:"ressource_humaine" %}
<div> <div class="d-flex flex-lg-column flex-row justify-content-between align-items-center">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalProjet"> <button class="btn btn-primary me-2 me-lg-0" data-bs-toggle="modal" data-bs-target="#modalProjet">
<i class="bi bi-plus-circle"></i> Ajouter un projet <i class="bi bi-plus-circle"></i> Ajouter un projet
</button> </button>
<button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#modalBailleur"> <button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#modalBailleur">

View File

@@ -1,20 +1,51 @@
<div class="modal fade" id="modalBailleur" tabindex="-1" aria-labelledby="modalBailleurLabel" aria-hidden="true"> <div class="modal fade" id="modalBailleur" tabindex="-1" aria-labelledby="modalBailleurLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog ">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalBailleurLabel">Ajouter un Bailleur</h5> <h5 class="modal-title" id="modalBailleurLabel">Gestion des Bailleurs</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body p-4"> <ul class="nav nav-tabs px-3 pt-2" id="bailleurTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="ajouter-tab" data-bs-toggle="tab"
data-bs-target="#ajouter" type="button" role="tab">
Ajouter
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="liste-tab" data-bs-toggle="tab"
data-bs-target="#liste" type="button" role="tab">
Liste
</button>
</li>
</ul>
<div class="modal-body">
<div class="tab-content pt-3">
<div class="tab-pane fade show active" id="ajouter" role="tabpanel">
<form id="formBailleur" method="POST" action="{% url 'gestion_projet:creation-bailleur' %}"> <form id="formBailleur" method="POST" action="{% url 'gestion_projet:creation-bailleur' %}">
{% csrf_token %} {% csrf_token %}
{{ form_ajout_bailleur.as_p }} {{ form_ajout_bailleur.as_p }}
<button type="submit" class="btn btn-success mt-3" id="btnEnregistrerBailleur">
<i class="bi bi-save me-1"></i> Enregistrer
</button>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="tab-pane fade" id="liste" role="tabpanel">
<button type="submit" id="btnEnregistrerBailleur" class="btn btn-success"><i class="bi bi-save me-1"></i> Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button> <div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Liste des bailleurs</h6>
</div>
<div class="row">
<div class="col">
<div id="table-bailleurs"
data-url="{% url 'gestion_projet:liste-bailleurs' %}">
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -31,23 +31,15 @@
<label>Date de fin :</label> <label>Date de fin :</label>
<input type="date" class="form-control" id="detail_date_fin" name='date_fin'> <input type="date" class="form-control" id="detail_date_fin" name='date_fin'>
</div> </div>
<div class="form-group mb-2">
<label> Type de projet </label>
<select class="form-select" id="detail_type_projet" name='type_projet'>
<option value="laboratoire">Laboratoire</option>
<option value="épidémiologie">Épidémiologie</option>
<option value="sciences sociales">Sciences sociales</option>
<option value="cliniques">Cliniques</option>
<option value="autre">Autre</option>
</select>
</div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label> Domaine de recherche </label> <label> Domaine de recherche </label>
<select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche"> <select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche">
<option value="sciences_sociales">Sciences sociales</option> <option value="sciences_sociales">Sciences sociales</option>
<option value="naturelles">Naturelles</option>
<option value="humaines">Humaines</option> <option value="humaines">Humaines</option>
<option value="veterinaires">Vétérinaires</option> <option value="epidemiologie">Epidémiologie</option>
<option value="sciences_biomedicales">Sciences biomédicales</option>
<option value="service_pedagogique">Service Pédagogique</option>
<option value="recherche_clinique">Recherche clinique</option>
</select> </select>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">

View File

@@ -19,6 +19,12 @@ urlpatterns = [
views.creation_projet, views.creation_projet,
name='creation-projet' name='creation-projet'
), ),
path(
'bailleurs/',
views.liste_bailleur,
name='liste-bailleurs'
),
path( path(
'projet/modifier/<int:projet_id>/', 'projet/modifier/<int:projet_id>/',
views.modification_projet, views.modification_projet,
@@ -114,4 +120,6 @@ urlpatterns = [
views.mises_a_jour_projet, views.mises_a_jour_projet,
name='mises-a-jour-projet' name='mises-a-jour-projet'
) )
] ]

View File

@@ -1,4 +1,4 @@
from datetime import datetime import datetime
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
from django.contrib import messages from django.contrib import messages
@@ -44,7 +44,6 @@ def liste_projet(request):
"date_fin": p.date_fin, "date_fin": p.date_fin,
"numero_convention": p.numero_convention, "numero_convention": p.numero_convention,
"description": p.description, "description": p.description,
"type_projet": p.type_projet,
"budget": p.budget, "budget": p.budget,
"budget_RH": p.budget_RH, "budget_RH": p.budget_RH,
"created_at": p.created_at, "created_at": p.created_at,
@@ -92,7 +91,7 @@ def index(request):
'nombre_personnel': nombre_personnel, 'nombre_personnel': nombre_personnel,
'budget_total': budget_total, 'budget_total': budget_total,
'formulaire_creation_projet': ProjetForm(), 'formulaire_creation_projet': ProjetForm(),
'projet_en_cours': Projet.objects.filter(date_fin__gte=datetime.date.today()).count(), 'projet_en_cours': Projet.objects.filter(date_fin__gte=datetime.datetime.now(tz=datetime.timezone.utc).date()).count(),
} }
return render(request, 'gestion_projet/index.html', context) return render(request, 'gestion_projet/index.html', context)
@@ -146,6 +145,22 @@ def creation_bailleur(request):
return JsonResponse({'success': True}) return JsonResponse({'success': True})
return JsonResponse({'success': False}) return JsonResponse({'success': False})
@login_required
def liste_bailleur(request):
""" Vue pour retourner la liste de tous les bailleurs """
bailleurs = Bailleur.objects.all().order_by('-id')
data = []
for b in bailleurs:
data.append({
"id": b.id,
"nom_organisme": b.nom_organisme,
"contact": b.contact,
"email": b.email,
"pays": b.pays,
})
return JsonResponse(data, safe=False)
@login_required @login_required
def ajouter_financement_projet(request): def ajouter_financement_projet(request):
"""Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%""" """Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%"""
@@ -321,6 +336,7 @@ def activites_projet(request):
} }
return render(request, 'gestion_projet/suivi_activite.html', context) return render(request, 'gestion_projet/suivi_activite.html', context)
@login_required @login_required
def ajouter_activite_projet(request): def ajouter_activite_projet(request):
"""Vue pour ajouter une activité à un projet spécifique via un formulaire""" """Vue pour ajouter une activité à un projet spécifique via un formulaire"""

View File

@@ -1,3 +1 @@
from django.contrib import admin from django.contrib import admin
# Register your models here.

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD # Generated by Django 5.2.13 on 2026-09-03 14:33
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View File

@@ -12,7 +12,12 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
modal.show(); modal.show();
fetch (`/gestion-salle/revervation/details/${event.guid}`) fetch (`/gestion-salle/revervation/details/${event.guid}`)
.then(response => response.json()) .then(response => {
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
return response.json();
})
.then(data => { .then(data => {
currentReservationId = data.id_reservation; currentReservationId = data.id_reservation;
$("id_reservation_detail").value = data.id_reservation; $("id_reservation_detail").value = data.id_reservation;
@@ -40,6 +45,7 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
} }
}) })
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
} }
}); });
@@ -86,9 +92,6 @@ $("bouton-annuler").addEventListener("click", (e) => {
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler; const url_annuler = $("formulaire-details").dataset.urlannuler;
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault(); e.preventDefault();
fetch( fetch(
@@ -165,7 +168,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
columns: [ columns: [
{title: "Employé", field: "employe"}, {title: "Employé", field: "employe"},
{title: "Salle", field: "salle"}, {title: "Salle", field: "salle"},
{title: "Date de l'evenement", field: "date_debut"}, {title: "Date de l'evenement", field: "date_evenement"},
{title: "Heure de début", field: "heure_debut"}, {title: "Heure de début", field: "heure_debut"},
{title: "Heure de fin", field: "heure_fin"}, {title: "Heure de fin", field: "heure_fin"},
{title: "Motif de reservation", field: "motif_reservation"}, {title: "Motif de reservation", field: "motif_reservation"},
@@ -177,11 +180,14 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => { tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData(); const data = rowData.getData();
console.log(data);
if(data.besoin_zoom === false){ if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
const id_user = $("current-user-id").dataset.userid; const id_user = $("current-user-id").dataset.userId;
$("id_reservation_detail").value = data.id; $("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id; $("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id; $("id_reservation_zoom").value = data.id;
@@ -189,8 +195,7 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe; $("employe").value=data.employe;
$("salle").value=data.salle; $("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut; $("statut-reservation").innerHTML=data.statut;
$("date_debut").value = data.date_debut; $("date_evenement").value = data.date_evenement;
$("date_fin").value = data.date_fin;
$("heure_debut").value=data.heure_debut; $("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin; $("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation; $("motif_reservation").value=data.motif_reservation;
@@ -198,6 +203,14 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("besoin_ordinateur").checked=data.besoin_ordi; $("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
const refuserReservation = document.querySelector("#refuserReservation");
const bouton_valider = document.querySelector("#bouton-valider");
if((refuserReservation | bouton_valider) && data.statut === "En attente"){
refuserReservation.style.display = "inline-block";
bouton_valider.style.display = "inline-block";
}
if (id_user != data.employe_id){ if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none"; $("bouton-annuler").className = "d-none";
} }

View File

@@ -16,14 +16,14 @@
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col py-2 px-4"> <div class="col py-2 px-4">
<h1 class="text-center">Reservation de salle</h1> <h1 class="text-center">Réservation de salle</h1>
<hr> <hr>
<div class="row d-flex justify-content-around"> <div class="row d-flex flex-column flex-lg-row justify-content-around">
<div class="form-group col-5 me-2"> <div class="form-group col col-lg-5 me-2">
<label>Selectionner le jour concerné :</label> <label>Selectionner le jour concerné :</label>
<input type="date" class="form-control" id="semaineDate" /> <input type="date" class="form-control" id="semaineDate" />
</div> </div>
<div class="form-group col-5 me-2"> <div class="form-group col col-lg-5 me-2">
<label>Selectionner une salle :</label> <label>Selectionner une salle :</label>
<select class = "form-select" id="liste-salle"> <select class = "form-select" id="liste-salle">
<option value='Salle de formation'>Salle de formation</option> <option value='Salle de formation'>Salle de formation</option>
@@ -33,9 +33,9 @@
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
<div class="col d-flex justify-content-center"> <div class="col d-flex flex-column flex-lg-row justify-content-center">
<button class="btn btn-lg btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle reservation</button> <button class="btn btn-lg btn-primary me-0 me-lg-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle réservation</button>
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#modalReservationAttente"> <button type="button" class="btn mt-2 mt-lg-0 btn-warning" data-bs-toggle="modal" data-bs-target="#modalReservationAttente">
Validation en attente <span class="badge badge-light">{{ nb_reservation_attente }}</span> Validation en attente <span class="badge badge-light">{{ nb_reservation_attente }}</span>
</button> </button>
</div> </div>
@@ -55,5 +55,5 @@
{% block 'js' %} {% block 'js' %}
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/index.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/index.min.js"></script>
<script src="{% static 'gestion_salle/js/index.js' %}"></script> <script src="{% static 'gestion_salle/js/index.js' %}?v=3"></script>
{% endblock %} {% endblock %}

View File

@@ -4,7 +4,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="modalReservationLabel"> <h1 class="modal-title fs-5" id="modalReservationLabel">
Nouvelle reservation Nouvelle réservation
</h1> </h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>

View File

@@ -5,14 +5,14 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="modalDetailReservationLabel"> <h1 class="modal-title fs-5" id="modalDetailReservationLabel">
Détails de la reservation (<span id='statut-reservation'></span>) Détails de la réservation (<span id='statut-reservation'></span>)
</h1> </h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form method="POST" data-urlannuler="{% url 'gestion_salle:annuler-reservation' %}" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details"> <form method="POST" data-urlannuler="{% url 'gestion_salle:annuler-reservation' %}" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details">
{% csrf_token %} {% csrf_token %}
<input type='' class="form-control" id="id_reservation_detail" name='id_reservation'> <input type='' class="form-control" id="id_reservation_detail" name='id_reservation' hidden>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Employé :</label> <label>Employé :</label>
<input class="form-control" id="employe" readonly> <input class="form-control" id="employe" readonly>
@@ -22,12 +22,8 @@
<input class="form-control" id="salle" readonly> <input class="form-control" id="salle" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Date de debut :</label> <label>Date de l'évènement :</label>
<input type='date' class="form-control" id="date_debut" readonly > <input type='date' class="form-control" id="date_evenement" readonly >
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type='date' class="form-control" id="date_fin" readonly >
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Heure de début :</label> <label>Heure de début :</label>
@@ -38,7 +34,7 @@
<input type='time' class="form-control" id="heure_fin" readonly > <input type='time' class="form-control" id="heure_fin" readonly >
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Motif de la reservation :</label> <label>Motif de la réservation :</label>
<textarea class="form-control" id="motif_reservation" readonly ></textarea> <textarea class="form-control" id="motif_reservation" readonly ></textarea>
</div> </div>
<div class="form-check mb-2"> <div class="form-check mb-2">
@@ -50,18 +46,23 @@
<input type="url" class="form-control" id="lien_zoom" readonly > <input type="url" class="form-control" id="lien_zoom" readonly >
</div> </div>
<div class="form-check mb-2"> <div class="form-check mb-2">
<label class="form-check-label">Besoin d'un ordinateur</label> <label class="form-check-label">Besoin d'un ordinateur {{ reservation.statut }}</label>
<input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly > <input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly >
</div> </div>
<div class='d-flex justify-content-around mt-2'> <hr />
{% if appartient_au_departement_informatique %} <div class='d-flex justify-content-center mt-2'>
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button> {% if est_equipe_informatique %}
<button class="btn btn-primary me-1" id="ajoutZoom">Ajout du lien zoom</button>
{% endif %} {% endif %}
{% if appartient_direction and reservation.statut == "en_attente" %} {% if est_assistante_direction %}
<button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button> <button class="btn btn-danger me-1" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
<button class="btn btn-success" id="bouton-valider">Valider</button> <button class="btn btn-success me-1" id="bouton-valider">Valider</button>
{% endif %} {% endif %}
<span id="current-user-id" data-user-id="{{ request.user.id }}"></span> {% if est_equipe_informatique %}
<button class="btn btn-danger me-1" id="refuserReservationInfo" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
<button class="btn btn-success me-1" id="bouton-valider-info">Valider</button>
{% endif %}
<span id="current-user-id" data-user-id="{{ user.id }}"></span>
<button class="btn btn-danger" id="bouton-annuler">Annuler</button> <button class="btn btn-danger" id="bouton-annuler">Annuler</button>
</div> </div>
</form> </form>

View File

@@ -4,7 +4,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="modalReservationAttenteLabel"> <h1 class="modal-title fs-5" id="modalReservationAttenteLabel">
Liste des reservation en attente de validation Liste des réservation en attente de validation
</h1> </h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>

View File

@@ -3,6 +3,7 @@ from datetime import timedelta
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.forms import model_to_dict from django.forms import model_to_dict
from django.http import HttpRequest, JsonResponse from django.http import HttpRequest, JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
@@ -16,13 +17,16 @@ from .models import Reservation
@login_required @login_required
def index(request: HttpRequest): def index(request: HttpRequest):
"""Vue d'affichage de la page d'accueil de la gestion des salles"""
try: try:
employe = Employe.objects.get(user=request.user) employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist: except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.") messages.error(request, "Profil employé introuvable.")
return redirect('gestion_conges:conge') return redirect('gestion_conges:conge')
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
if request.method == "POST": if request.method == "POST":
form = ReservationForm(request.POST) form = ReservationForm(request.POST)
@@ -37,11 +41,11 @@ def index(request: HttpRequest):
besoin_ordi = form.cleaned_data['besoin_ordi'] besoin_ordi = form.cleaned_data['besoin_ordi']
if date_fin < date_debut: if date_fin < date_debut:
messages.error(request, "Date fin invalide.") messages.error(request, "La date de fin est antérieure à la date de début.")
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
if heure_fin <= heure_debut: if heure_fin <= heure_debut:
messages.error(request, "Heure invalide.") messages.error(request, "L'heure de fin est antérieure à l'heure de début.")
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
if not request.user.first_name.strip() or not request.user.last_name.strip(): if not request.user.first_name.strip() or not request.user.last_name.strip():
@@ -54,8 +58,9 @@ def index(request: HttpRequest):
created = [] created = []
current_date = date_debut current_date = date_debut
while current_date <= date_fin: print(f"Création de réservations pour {employe}")
while current_date <= date_fin:
reservation = Reservation.objects.create( reservation = Reservation.objects.create(
employe=employe, employe=employe,
date_debut=current_date, date_debut=current_date,
@@ -74,20 +79,35 @@ def index(request: HttpRequest):
messages.success(request, "Réservation(s) créée(s) avec succès.") messages.success(request, "Réservation(s) créée(s) avec succès.")
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION: # Nouvelle logique d'envoi de mails à la création :
# 1. Une demande de salle (formation/réunion) est envoyée à l'assistante de direction pour validation.
# - Si elle nécessite un lien Zoom ou un ordinateur, l'équipe informatique sera notifiée
# seulement après la validation par l'assistante.
# 2. Une demande de lien Zoom directement est envoyée à l'équipe informatique.
if salle != "lien_zoom" and fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
fonctions_utilitaire.envoyer_mail( fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle", sujet="Demande de réservation de salle",
message = f""" message=f"""Bonjour,
Une nouvelle demande de réservation de la {dict(Reservation.TYPE_CHOICES).get(salle)} a été effectuée par {employe.user.first_name} {employe.user.last_name} du {form.cleaned_data.get('date_debut').strftime('%d/%m/%Y')} au {form.cleaned_data.get('date_fin').strftime('%d/%m/%Y')} pour motif "{motif}". Une nouvelle demande de réservation de la {dict(Reservation.TYPE_CHOICES).get(salle)} a été effectuée par {employe.user.first_name} {employe.user.last_name} du {form.cleaned_data.get('date_debut').strftime('%d/%m/%Y')} au {form.cleaned_data.get('date_fin').strftime('%d/%m/%Y')} pour motif "{motif}".
{"Cette demande nécessite un lien Zoom et/ou un ordinateur et sera traitée par l'équipe informatique après votre validation." if besoin_zoom or besoin_ordi else ""}
Veuillez vous connecter à la plateforme pour plus de détails.""", Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires = fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION destinataires=fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
)
if salle == "lien_zoom" and fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
fonctions_utilitaire.envoyer_mail(
sujet="Demande de lien Zoom",
message=f"""Bonjour,
Une nouvelle demande de lien Zoom a été effectuée par {employe.user.first_name} {employe.user.last_name} du {date_debut.strftime('%d/%m/%Y')} au {date_fin.strftime('%d/%m/%Y')} de {heure_debut.strftime('%H:%M')} à {heure_fin.strftime('%H:%M')} pour le motif {motif}.
Veuillez vous connecter à la plateforme pour traiter cette demande.""",
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
) )
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
formulaire_reservation = ReservationForm()
departement = employe.departement
appartient_direction = request.user.groups.filter(name='direction').exists() formulaire_reservation = ReservationForm()
# departement = employe.departement
liste_demande_reservation = Reservation.objects.filter( liste_demande_reservation = Reservation.objects.filter(
employe=employe, employe=employe,
@@ -97,8 +117,9 @@ Veuillez vous connecter à la plateforme pour plus de détails.""",
context = { context = {
'formulaire_reservation': formulaire_reservation, 'formulaire_reservation': formulaire_reservation,
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(), 'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique", 'appartient_au_departement_informatique': est_equipe_info,
'appartient_direction': appartient_direction, 'est_assistante_direction': est_assistante,
'est_equipe_informatique': est_equipe_info,
'liste_demande_reservation': list(liste_demande_reservation), 'liste_demande_reservation': list(liste_demande_reservation),
} }
@@ -108,8 +129,8 @@ def liste_reservation(request:HttpRequest):
"""Vue d'affichage des creneaux disponibles""" """Vue d'affichage des creneaux disponibles"""
reservations = Reservation.objects.filter(statut = "validee") reservations = Reservation.objects.filter(statut = "validee")
liste_reservation = [] liste_reservation = []
for reservation in reservations: for reservation in reservations:
print(reservation.statut)
color = None color = None
if reservation.statut == "en_attente": if reservation.statut == "en_attente":
color = "#ffc107" color = "#ffc107"
@@ -130,7 +151,35 @@ def liste_reservation(request:HttpRequest):
@login_required @login_required
def liste_reservation_attente(request): def liste_reservation_attente(request):
"""Liste des réservations en attente, filtrée selon le profil :
- Assistante de direction : voit les salles à valider (pas les liens choisis).
- Équipe informatique : voit les liens choisis et les salles demandant zoom/ordi non traités.
"""
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
return JsonResponse({"success": False, "message": "Profil employé introuvable"})
est_informatique = fonctions_utilitaire.est_equipe_informatique(employe)
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
reservations = Reservation.objects.filter(statut="en_attente") reservations = Reservation.objects.filter(statut="en_attente")
if est_assistante:
# L'assistante voit les demandes de salle (formation / réunion)
# Elle ne reçoit pas les demandes de simple lien (lien_zoom) qui vont à l'équipe info.
reservations = reservations.exclude(salle="lien_zoom")
elif est_informatique:
# L'équipe informatique voit :
# - les demandes de lien zoom directs
# - les demandes de salle avec besoin_zoom ou besoin_ordi (déjà validés par l'assistance)
# pour lesquelles le lien_zoom n'a pas encore était fourni (lien_zoom vide)
reservations = reservations.filter(
Q(salle="lien_zoom") | Q(besoin_zoom=True) | Q(besoin_ordi=True)
)
print("Test :", [reservation.employe for reservation in reservations])
liste_reservation = [ liste_reservation = [
{ {
**model_to_dict(reservation), **model_to_dict(reservation),
@@ -138,21 +187,20 @@ def liste_reservation_attente(request):
"employe_id": reservation.employe.user.id, "employe_id": reservation.employe.user.id,
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle), "salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
'statut': dict(Reservation.STATUT).get(reservation.statut), 'statut': dict(Reservation.STATUT).get(reservation.statut),
"date_evenement": reservation.date_debut.strftime('%Y-%m-%d'),
} for reservation in reservations } for reservation in reservations
] ]
return JsonResponse(liste_reservation, safe=False) return JsonResponse(liste_reservation, safe=False)
def detail_reservation(request:HttpRequest, reservation_id:int): def detail_reservation(request:HttpRequest, reservation_id:int):
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
employe = reservation.employe.user employe = reservation.employe.user
reservation_json = { reservation_json = {
'id_reservation': reservation_id, 'id_reservation': reservation_id,
'employe': f"{employe.first_name} {employe.last_name}", 'employe': f"{employe.first_name} {employe.last_name}",
'salle': reservation.salle, 'salle': dict(Reservation.TYPE_CHOICES).get(reservation.salle),
'statut': dict(Reservation.STATUT).get(reservation.statut), 'statut': dict(Reservation.STATUT).get(reservation.statut),
'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'), 'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'),
'heure_debut': reservation.heure_debut.strftime('%H:%M'), 'heure_debut': reservation.heure_debut.strftime('%H:%M'),
@@ -167,49 +215,99 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
@login_required @login_required
def ajouter_lien_zoom(request:HttpRequest): def ajouter_lien_zoom(request:HttpRequest):
"""Vue de gestion de l'ajout du lien zoom""" """Traitement de la réservation par l'équipe informatique :
- Ajoute lien zoom si demandé.
- Valide une réservation qui exigeait un traitement par l'équipe informatique.
- Envoie un mail à toutes les équipes et au demandeur après validation.
"""
reservation = None
if request.method == 'POST': if request.method == 'POST':
reservation_id= request.POST['id_reservation'] reservation_id = request.POST.get('id_reservation')
try: try:
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
except reservation.DoesNotExist: except (Reservation.DoesNotExist, ValueError):
messages.error(request, "La resevertion selectionné n'existe pas.") messages.error(request, "La réservation sélectionnée n'existe pas.")
return redirect("salle")
if not (reservation.besoin_zoom or reservation.salle == "lien_zoom"):
messages.error(request, "Cette réservation ne nécessite pas de lien Zoom.")
return redirect('gestion_salle:reservation-salle')
elif reservation.statut in ["annulee", "refusee"]:
messages.error(request, "Le lien Zoom ne peut être ajouté pour les réservations annulée ou refusée.")
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
if request.method == "POST": try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.")
return redirect('gestion_salle:reservation-salle')
# Vérification : seule l'équipe informatique peut traiter ces demandes
if not fonctions_utilitaire.est_equipe_informatique(employe):
messages.error(request, "Vous n'êtes pas autorisé à traiter cette demande.")
return redirect('gestion_salle:reservation-salle')
if reservation.salle == "lien_zoom" or reservation.besoin_zoom:
lien = request.POST.get("lien_zoom") lien = request.POST.get("lien_zoom")
if not lien:
messages.error(request, "Veuillez fournir un lien Zoom.")
return redirect('gestion_salle:reservation-salle')
reservation.lien_zoom = lien reservation.lien_zoom = lien
reservation.statut = "validee" reservation.statut = "validee"
reservation.save() reservation.save()
messages.success(request, "Le lien Zoom a été ajouté et la réservation a été validée. ") messages.success(request, "La demande a été traitée et validée. ")
# Mail au demandeur
fonctions_utilitaire.envoyer_mail(
sujet="Réservation traitée",
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)}{' avec lien Zoom' if reservation.lien_zoom else ''} a été traitée. Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires=[reservation.employe.user.email]
)
# Mail aux emails de l'équipe informatique (hors utilisateur courant)
autres_membres = Employe.objects.filter(departement__nom="Systeme informatique").exclude(id=employe.id).values_list('user__email', flat=True)
if autres_membres:
fonctions_utilitaire.envoyer_mail(
sujet="Réservation validée par l'équipe informatique",
message=f"""Bonjour,
La réservation {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} a été traitée et validée par {employe.user.first_name} {employe.user.last_name}.\n\nVeuillez vous connecter à la plateforme pour plus de détails.""",
destinataires=list(autres_membres)
)
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
@login_required @login_required
def annuler_reservation(request:HttpRequest): def annuler_reservation(request:HttpRequest):
"""Vue de gestion de l'annulation de la reservation""" """Vue de gestion de l'annulation de la reservation, réservée à l'employé demandeur ou aux validateurs."""
if request.method == 'POST': if request.method == 'POST':
reservation_id= request.POST['id_reservation'] reservation_id= request.POST['id_reservation']
try: try:
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist: except Reservation.DoesNotExist:
messages.error(request, "La resevertion selectionné n'existe pas.") messages.error(request, "La reservation selectionné n'existe pas.")
return redirect("salle") return redirect("gestion_salle:reservation-salle")
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.")
return redirect("gestion_salle:reservation-salle")
# Seul l'employé demandeur ou un validateur (assistante / équipe info) peut annuler
est_demandeur = reservation.employe == employe
est_validateur = fonctions_utilitaire.est_assistante_direction(employe) or fonctions_utilitaire.est_equipe_informatique(employe)
if not est_demandeur and not est_validateur:
messages.error(request, "Vous n'êtes pas autorisé à annuler cette réservation.")
return redirect('gestion_salle:reservation-salle')
reservation.statut = 'annulee' reservation.statut = 'annulee'
reservation.save() reservation.save()
messages.success(request, "Votre réservation a été annulée.") messages.success(request, "La réservation a été annulée.")
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
@login_required @login_required
def valider_reservation(request: HttpRequest): def valider_reservation(request: HttpRequest):
"""Validation d'une réservation""" """Validation d'une réservation selon le profil :
- Assistante de direction : valide les demandes de salles (sauf lien_zoom) ;
- Équipe informatique : valide les demandes de lien_zoom et les
demandes de salle avec besoin zoom/ordi restées en attente après la première correction.
"""
if request.method == 'POST': if request.method == 'POST':
reservation_id = request.POST.get('id_reservation') reservation_id = request.POST.get('id_reservation')
@@ -217,36 +315,121 @@ def valider_reservation(request: HttpRequest):
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist: except Reservation.DoesNotExist:
messages.error(request, "La réservation sélectionnée n'existe pas.") messages.error(request, "La réservation sélectionnée n'existe pas.")
return redirect("salle") return redirect('gestion_salle:reservation-salle')
reservation.statut = 'validee' try:
reservation.save() employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.")
return redirect('gestion_salle:reservation-salle')
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
if est_assistante:
# L'assistante ne doit pas traiter les simple lien_zoom
if reservation.salle == "lien_zoom":
messages.error(request, "Les demandes de lien Zoom sont traitées par l'équipe informatique.")
return redirect('gestion_salle:reservation-salle')
if reservation.besoin_zoom or reservation.besoin_ordi:
# La demande reste en attente, l'équipe informatique doit être notifiée
# L'assistante a donné sa validation "salle", on garde le statut "en_attente"
# pour que l'équipe informatique puisse traiter, et on envoie le mail à l'équipe.
if fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
fonctions_utilitaire.envoyer_mail( fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle", sujet="Réservation de salle à traiter par l'équipe informatique",
message = f"""Bonjour {request.user.first_name} {request.user.last_name}, votre reservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été validée. Veuillez vous connecter à la plateforme pour plus de détails.""", message=f"""Bonjour,
destinataires = [reservation.employe.user.email] L'assistante de direction a validé la réservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} le {reservation.date_debut.strftime('%d/%m/%Y')} (de {reservation.heure_debut.strftime('%H:%M')} à {reservation.heure_fin.strftime('%H:%M')}).
Cette demande nécessite {"un lien zoom" if reservation.besoin_zoom else ""}{" - un ordinateur" if reservation.besoin_ordi else ""}.
Veuillez vous connecter à la plateforme pour traiter cette demande.""",
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
) )
messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} validée avec succès.") messages.success(request, "Réservation validée, une demande a été envoyée à l'équipe informatique.")
reservation.statut = "en_attente" # reste en attente pour l'équipe info
reservation.save()
else:
# Validation directe sans traitement informatique
reservation.statut = "validee"
reservation.save()
fonctions_utilitaire.envoyer_mail(
sujet="Réservation de salle validée",
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre réservation de la {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été validée. Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires=[reservation.employe.user.email]
)
elif est_equipe_info:
# L'équipe informatique traite le reste : lien_zoom et besoins info
if reservation.salle == "lien_zoom" or reservation.besoin_zoom or reservation.besoin_ordi:
if reservation.besoin_zoom and not reservation.lien_zoom:
# La demande exige un lien zoom qui n'a pas été encore rempli,
# il est préférable de passer par ajouter_lien_zoom. On laisse traiter.
messages.success(request, "Veuillez ajouter le lien Zoom via le champs dédié.")
return redirect('gestion_salle:reservation-salle')
reservation.statut = "validee"
reservation.save()
# Mail au demandeur + autres membres de l'équipe
fonctions_utilitaire.envoyer_mail(
sujet="Réservation validée",
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} a été traitée et validée. Veuillez vous connecter pour plus de détails.""",
destinataires=[reservation.employe.user.email]
)
autres = Employe.objects.filter(departement__nom="Systeme informatique").exclude(id=employe.id).values_list('user__email', flat=True)
if autres:
fonctions_utilitaire.envoyer_mail(
sujet="Réservation traitée par un collègue",
message=f"""Bonjour {request.user.first_name} {request.user.last_name}, la demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} a été traitée et validée par {employe.user.first_name} {employe.user.last_name}. Veuillez vous connecter pour plus de détails.""",
destinataires=list(autres)
)
else:
messages.error(request, "Cette réservation ne relève pas de l'équipe informatique.")
return redirect('gestion_salle:reservation-salle')
else:
messages.error(request, "Vous n'êtes pas autorisé à effectuer cette validation.")
return redirect('gestion_salle:reservation-salle')
messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} a été validée avec succès.")
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
@login_required @login_required
def refuser_reservation(request:HttpRequest): def refuser_reservation(request:HttpRequest):
"""Vue de gestion de refus de la reservation""" """Vue de gestion de refus de la reservation, autorisée à l'assistant de direction
et l'équipe informatique selon le type de demande."""
data = json.loads(request.body) data = json.loads(request.body)
reservation_id = data.get("id_reservation") reservation_id = data.get("id_reservation")
try: try:
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist: except Reservation.DoesNotExist:
return JsonResponse({"message": "La resevertion selectionné n'existe pas."}) return JsonResponse({"message": "La reservation selectionné n'existe pas."})
else:
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
return JsonResponse({"message": "Profil employé introuvable."})
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
peut_refuser = False
if est_assistante and reservation.salle != "lien_zoom":
peut_refuser = True
elif est_equipe_info and (reservation.salle == "lien_zoom" or reservation.besoin_zoom or reservation.besoin_ordi):
peut_refuser = True and (not reservation.besoin_zoom or reservation.lien_zoom)
if not peut_refuser:
return JsonResponse({"message": "Vous n'êtes pas autorisé à refuser cette réservation."})
reservation.statut = "refusee" reservation.statut = "refusee"
reservation.save() reservation.save()
fonctions_utilitaire.envoyer_mail( fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle", sujet = "Réservation refusée",
message = f"""Bonjour {request.user.first_name} {request.user.last_name}, votre reservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été refusée. Veuillez vous connecter à l'Assistante de Direction pour plus de détails.""", message = f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de réservation du {reservation.date_debut.strftime('%d/%m/%Y')} de {reservation.date_debut.strftime('%d/%m/%Y')} de {reservation.heure_debut.strftime('%H:%M')} à {reservation.heure_fin.strftime('%H:%M')} a été refusée.""",
destinataires = [reservation.employe.user.email] destinataires = [reservation.employe.user.email]
) )

View File

@@ -1,4 +1,3 @@
<<<<<<< HEAD
asgiref==3.11.1 asgiref==3.11.1
certifi==2026.4.22 certifi==2026.4.22
charset-normalizer==3.4.7 charset-normalizer==3.4.7
@@ -21,159 +20,3 @@ tzdata==2026.2
urllib3==2.6.3 urllib3==2.6.3
webservices==0.7 webservices==0.7
webservices.naudit==0.7.2 webservices.naudit==0.7.2
python-decouple
gunicorn
=======
anyio==4.13.0
argon2-cffi==25.1.0
argon2-cffi-bindings==25.1.0
arrow==1.4.0
asgiref==3.11.0
asttokens==3.0.1
async-lru==2.3.0
attrs==26.1.0
Automat==20.2.0
babel==2.18.0
bcrypt==3.2.0
beautifulsoup4==4.14.3
bleach==6.3.0
blinker==1.4
certifi==2026.2.25
cffi==2.0.0
chardet==4.0.0
charset-normalizer==3.4.7
click==8.0.3
cloud-init==25.3
colorama==0.4.4
comm==0.2.3
command-not-found==0.3
configobj==5.0.6
constantly==15.1.0
cryptography==3.4.8
dbus-python==1.2.18
debugpy==1.8.20
decorator==5.2.1
defusedxml==0.7.1
distlib==0.4.0
distro==1.7.0
distro-info==1.1+ubuntu0.2
Django==5.2.10
et_xmlfile==2.0.0
exceptiongroup==1.3.1
executing==2.2.1
fastjsonschema==2.21.2
filelock==3.20.3
fqdn==1.5.1
h11==0.16.0
httpcore==1.0.9
httplib2==0.20.2
httpx==0.28.1
hyperlink==21.0.0
idna==3.3
importlib-metadata==4.6.4
incremental==21.3.0
ipykernel==7.2.0
ipython==8.39.0
isoduration==20.11.0
jedi==0.19.2
jeepney==0.7.1
Jinja2==3.0.3
json5==0.14.0
jsonpatch==1.32
jsonpointer==2.0
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
jupyter-events==0.12.0
jupyter-lsp==2.3.1
jupyter_client==8.8.0
jupyter_core==5.9.1
jupyter_server==2.17.0
jupyter_server_terminals==0.5.4
jupyterlab==4.5.6
jupyterlab_pygments==0.3.0
jupyterlab_server==2.28.0
keyring==23.5.0
lark==1.3.1
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
MarkupSafe==2.0.1
matplotlib-inline==0.2.1
mistune==3.2.0
more-itertools==8.10.0
nbclient==0.10.4
nbconvert==7.17.0
nbformat==5.10.4
nest-asyncio==1.6.0
netifaces==0.11.0
notebook_shim==0.2.4
numpy==2.2.6
oauthlib==3.2.0
openpyxl==3.1.5
overrides==7.7.0
packaging==26.0
pandas==2.3.3
pandocfilters==1.5.1
parso==0.8.6
pexpect==4.9.0
platformdirs==4.5.1
prometheus_client==0.24.1
prompt_toolkit==3.0.52
psutil==7.2.2
ptyprocess==0.7.0
pure_eval==0.2.3
pyasn1==0.4.8
pyasn1-modules==0.2.1
pycparser==3.0
pycurl==7.44.1
Pygments==2.20.0
PyGObject==3.42.1
PyHamcrest==2.0.2
PyJWT==2.3.0
pyOpenSSL==21.0.0
pyparsing==2.4.7
pyrsistent==0.18.1
pyserial==3.5
python-apt==2.4.0+ubuntu4.1
python-dateutil==2.9.0.post0
python-json-logger==4.1.0
pytz==2022.1
PyYAML==5.4.1
pyzmq==27.1.0
referencing==0.37.0
requests==2.33.1
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rfc3987-syntax==1.1.0
rpds-py==0.30.0
SecretStorage==3.3.1
Send2Trash==2.1.0
service-identity==18.1.0
six==1.16.0
soupsieve==2.8.3
sqlparse==0.5.5
ssh-import-id==5.11
stack-data==0.6.3
systemd-python==234
terminado==0.18.1
tinycss2==1.4.0
tomli==2.4.1
tornado==6.5.5
traitlets==5.14.3
Twisted==22.1.0
typing_extensions==4.15.0
tzdata==2025.3
ubuntu-pro-client==8001
ufw==0.36.1
unattended-upgrades==0.1
uri-template==1.3.0
urllib3==1.26.5
virtualenv==20.13.0+ds
wadllib==1.3.6
wcwidth==0.6.0
webcolors==25.10.0
webencodings==0.5.1
websocket-client==1.9.0
zipp==1.0.0
zope.interface==5.4.0
>>>>>>> c28b14f (clean: remove pycache from tracking)

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@@ -1,6 +0,0 @@
from django.apps import AppConfig
class GestionCongeConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'gestion_conge'

View File

@@ -3,19 +3,6 @@ const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url const url_liste_conge_attente = $("liste-demande-conges").dataset.url
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// layout : "fitColumns",
=======
layout : "fitColumns",
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
layout : "fitColumns",
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
layout : "fitColumns",
>>>>>>> 6026af5 (Bug : Affichages des conges)
columns: [ columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"}, {"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{ {"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
@@ -27,19 +14,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
outputFormat:"dd/MM/yy", outputFormat:"dd/MM/yy",
}}, }},
{"title": "Type de congé", "field": "type"}, {"title": "Type de congé", "field": "type"},
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
{"title": "Date de la demande", "field": "date_demande", responsive: 1}, {"title": "Date de la demande", "field": "date_demande", responsive: 1},
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> 6026af5 (Bug : Affichages des conges)
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{ {"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
allowEmpty : true , allowEmpty : true ,
}}, }},
@@ -48,19 +23,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}}, }},
], ],
pagination: true, pagination: true,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
paginationSize: 5, paginationSize: 5,
=======
paginationSize: 5
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
paginationSize: 5
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
paginationSize: 5
>>>>>>> 6026af5 (Bug : Affichages des conges)
}) })
const bouton_demande_conges = $("bouton-demande-conge"); const bouton_demande_conges = $("bouton-demande-conge");

View File

@@ -9,26 +9,15 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
{"title": "Projet", "field": "projet"}, {"title": "Projet", "field": "projet"},
{"title": "Téléphone", "field": "telephone"}, {"title": "Téléphone", "field": "telephone"},
], ],
// ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
responsiveLayout : "hide" responsiveLayout : "hide"
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
}) })
fetch(url_liste_employe) fetch(url_liste_employe)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success){ if (data.success){
console.log(data.data)
tableau_liste_employe.setData(data.data); tableau_liste_employe.setData(data.data);
}else{ }else{
alert(data.message); alert(data.message);
@@ -38,6 +27,7 @@ fetch(url_liste_employe)
tableau_liste_employe.on("rowClick", function (row, rowData) { tableau_liste_employe.on("rowClick", function (row, rowData) {
const data = rowData.getData(); const data = rowData.getData();
document.getElementById('detail-id').value = data.id; document.getElementById('detail-id').value = data.id;
document.getElementById('detail-matricule').value = data.matricule; document.getElementById('detail-matricule').value = data.matricule;
document.getElementById('detail-employe').value = data.employe; document.getElementById('detail-employe').value = data.employe;
@@ -220,23 +210,31 @@ $("enregistrerDetail").addEventListener("click", function() {
const fonction = document.getElementById('detail-fonction').value; const fonction = document.getElementById('detail-fonction').value;
const dateEmbauche = document.getElementById('detail-dateEmbauche').value; const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
const matricule = document.getElementById('detail-matricule').value; const matricule = document.getElementById('detail-matricule').value;
const departement = document.getElementById('detail-departement').value;
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url;
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset)
const payload = {
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule,
"departement": departement
};
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
fetch(url_enregistrer_detail, { fetch(url_enregistrer_detail, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value "X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
}, },
body: JSON.stringify({ body: JSON.stringify(payload)
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule
})
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
console.log('Réponse du serveur:', data);
alert(data.message); alert(data.message);
location.reload(); location.reload();
}) })
@@ -271,3 +269,69 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
], ],
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants, ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
}) })
/* let tabContrats = null;
document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () {
const employeId = document.getElementById("detail-id").value;
if (!employeId) {
alert("Aucun employé sélectionné");
return;
}
fetch(`/employé/employe/${employeId}/contrats/`)
.then(response => {
if (!response.ok) {
throw new Error("Erreur HTTP " + response.status);
}
return response.json();
})
.then(data => {
document.getElementById("nom-employe-contrat").innerText =
data.employe;
if (tabContrats) {
tabContrats.destroy();
}
tabContrats = new Tabulator(
"#table-contrats-tabulator",
{
data: data.contrats,
layout: "fitColumns",
placeholder: "Aucun contrat",
columns: [
{ title: "Numéro", field: "numero_contrat" },
{ title: "Type", field: "type_contrat" },
{ title: "Date début", field: "date_debut" },
{ title: "Date fin", field: "date_fin" },
{ title: "Salaire", field: "salaire_mensuel" },
{ title: "Statut", field: "statut" },
{
title: "Contrat",
field: "fichier",
formatter: function(cell) {
const url = cell.getValue();
if (!url) {
return "<span class='text-danger'>Aucun fichier</span>";
}
return `<a href="${url}" target="_blank" class="btn btn-sm btn-primary">
<i class="bi bi-file-earmark-pdf"></i> Ouvrir
</a>`;
}
}
]
}
);
})
.catch(error => {
console.error("Erreur :", error);
});
}); */

View File

@@ -13,21 +13,20 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
inputFormat:"yyyy-MM-dd", inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy", outputFormat:"dd/MM/yy",
}}, }},
{"title": "Fichier", "field": "certificat", formatter:"link", formatterParams:{
label:"Voir le fichier",
target:"_blank",
}},
], ],
ajaxURL: url_certificat, ajaxURL: url_certificat,
}) })
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
const enregistrerProfil = $("enregistrerProfil"); const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => { enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url; const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value; const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData(); const formData = new FormData();
formData.append("nom", $("nom").value); formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value); formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value); formData.append("email", $("email").value);
@@ -35,6 +34,10 @@ enregistrerProfil.addEventListener("click", (e) => {
formData.append("adresse", $("adresse").value); formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value); formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value); formData.append("date_naissance", $("date_naissance").value);
const photoInput = $("photo");
if (photoInput.files.length > 0) {
formData.append("photo", photoInput.files[0]);
}
fetch(url, { fetch(url, {
method: "POST", method: "POST",
@@ -47,41 +50,3 @@ enregistrerProfil.addEventListener("click", (e) => {
.then(data => alert(data.message)) .then(data => alert(data.message))
.catch(error => console.error("Erreur:", error)); .catch(error => console.error("Erreur:", error));
}); });
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({
"nom": $("nom").value,
"prenom": $("prenom").value,
"email": $("email").value,
"telephone": $("telephone").value,
"adresse": $("adresse").value,
"sexe": $("sexe").value,
"date_naissance": $("date_naissance").value,
})
})
.then(response => response.json())
.then(data => alert(data.message))
<<<<<<< HEAD
<<<<<<< HEAD
})
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
})
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
})
>>>>>>> 6026af5 (Bug : Affichages des conges)

View File

@@ -1,14 +1,5 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
let table; let table;
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
btnEnregistrerBailleur.addEventListener('click', function() { btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur'); const form = document.getElementById('formBailleur');
@@ -30,14 +21,10 @@ btnEnregistrerBailleur.addEventListener('click', function() {
alert('Ce bailleur existe déjà dans la base de données.'); alert('Ce bailleur existe déjà dans la base de données.');
} }
}); });
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
}); });
document.addEventListener("DOMContentLoaded", function () {
table = new Tabulator("#table-bailleurs", { table = new Tabulator("#table-bailleurs", {
ajaxURL: "/gestion-projet/bailleurs/", ajaxURL: "/gestion-projet/bailleurs/",
layout: "fitColumns", layout: "fitColumns",
pagination: "local", pagination: "local",
@@ -57,16 +44,4 @@ document.addEventListener("DOMContentLoaded", function () {
supprimerBailleur(data.id); supprimerBailleur(data.id);
} }
} }
});
}); });
=======
});
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
});
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
});
>>>>>>> 6026af5 (Bug : Affichages des conges)

View File

@@ -1,5 +1,6 @@
const $ = (element) => document.getElementById(element) const $ = (element) => document.getElementById(element)
const url_liste_projet = $("tableau-liste-projet").dataset.url; const url_liste_projet = $("tableau-liste-projet").dataset.url;
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", { const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
fitColumns: true, fitColumns: true,
responsiveLayout : true, responsiveLayout : true,
@@ -39,7 +40,6 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
$("detail_date_debut").value = data.date_debut; $("detail_date_debut").value = data.date_debut;
$("detail_date_fin").value = data.date_fin; $("detail_date_fin").value = data.date_fin;
$("detail_numero_convention").value = data.numero_convention; $("detail_numero_convention").value = data.numero_convention;
$("detail_type_projet").value = data.type_projet;
Array.from($("detail_domaine_recherche").options).forEach(option => { Array.from($("detail_domaine_recherche").options).forEach(option => {
if (data.domaine_recherche.includes(option.value)) { if (data.domaine_recherche.includes(option.value)) {
option.selected = true; option.selected = true;

View File

@@ -12,21 +12,14 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
modal.show(); modal.show();
fetch (`/gestion-salle/revervation/details/${event.guid}`) fetch (`/gestion-salle/revervation/details/${event.guid}`)
.then(response => response.json()) .then(response => {
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
return response.json();
})
.then(data => { .then(data => {
currentReservationId = data.id_reservation; currentReservationId = data.id_reservation;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
console.log(data);
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
console.log(data);
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
console.log(data);
>>>>>>> 6026af5 (Bug : Affichages des conges)
$("id_reservation_detail").value = data.id_reservation; $("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation; $("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation; $("id_reservation_zoom").value = data.id_reservation;
@@ -41,31 +34,18 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur; $("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD const zoomContainer = $("lien_zoom_container");
<<<<<<< HEAD if (!data.besoin_zoom) {
<<<<<<< HEAD zoomContainer.classList.add("d-none");
} else {
zoomContainer.classList.remove("d-none");
}
if (data.besoin_zoom === false){ if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none"; $("lien_zoom_container").className = "d-none";
} }
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
}
<<<<<<< HEAD
<<<<<<< HEAD
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
}) })
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
} }
}); });
@@ -107,23 +87,13 @@ document.addEventListener("DOMContentLoaded", function () {
}) })
$("bouton-annuler").addEventListener("click", (e) => { $("bouton-annuler").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler; const url_annuler = $("formulaire-details").dataset.urlannuler;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault(); e.preventDefault();
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
fetch( fetch(
url_annuler, url_annuler,
{ {
@@ -170,22 +140,15 @@ if($("ajoutZoom")){
if($("refuserReservation")){ if($("refuserReservation")){
$("refuserReservation").addEventListener("click", (e) => { $("refuserReservation").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url = e.currentTarget.dataset.lienrefus; const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value; const idRes = $("id_reservation_detail").value;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
console.log(idRes); console.log(idRes);
e.preventDefault(); e.preventDefault();
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
fetch(url, { fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@@ -205,7 +168,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
columns: [ columns: [
{title: "Employé", field: "employe"}, {title: "Employé", field: "employe"},
{title: "Salle", field: "salle"}, {title: "Salle", field: "salle"},
{title: "Date de l'evenement", field: "date_debut"}, {title: "Date de l'evenement", field: "date_evenement"},
{title: "Heure de début", field: "heure_debut"}, {title: "Heure de début", field: "heure_debut"},
{title: "Heure de fin", field: "heure_fin"}, {title: "Heure de fin", field: "heure_fin"},
{title: "Motif de reservation", field: "motif_reservation"}, {title: "Motif de reservation", field: "motif_reservation"},
@@ -217,45 +180,14 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => { tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData(); const data = rowData.getData();
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
console.log(data); console.log(data);
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
console.log(data);
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
console.log(data);
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.besoin_zoom === false){ if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
<<<<<<< HEAD const id_user = $("current-user-id").dataset.userId;
<<<<<<< HEAD
<<<<<<< HEAD
const id_user = $("current-user-id").dataset.userid;
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
<<<<<<< HEAD
<<<<<<< HEAD
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
$("id_reservation_detail").value = data.id; $("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id; $("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id; $("id_reservation_zoom").value = data.id;
@@ -263,42 +195,25 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe; $("employe").value=data.employe;
$("salle").value=data.salle; $("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut; $("statut-reservation").innerHTML=data.statut;
<<<<<<< HEAD $("date_evenement").value = data.date_evenement;
<<<<<<< HEAD
<<<<<<< HEAD
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
=======
$("date_evenement").value=data.date_debut;
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
$("date_evenement").value=data.date_debut;
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
$("date_evenement").value=data.date_debut;
>>>>>>> 6026af5 (Bug : Affichages des conges)
$("heure_debut").value=data.heure_debut; $("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin; $("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation; $("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom; $("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi; $("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD
<<<<<<< HEAD const refuserReservation = document.querySelector("#refuserReservation");
<<<<<<< HEAD const bouton_valider = document.querySelector("#bouton-valider");
if((refuserReservation | bouton_valider) && data.statut === "En attente"){
refuserReservation.style.display = "inline-block";
bouton_valider.style.display = "inline-block";
}
if (id_user != data.employe_id){ if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none"; $("bouton-annuler").className = "d-none";
} }
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> 6026af5 (Bug : Affichages des conges)
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide(); bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();

File diff suppressed because it is too large Load Diff

View File

@@ -1,278 +0,0 @@
/* === Print === */
@media print {
nav, .btn, .sidebar, .form-select, .form-control {
display: none !important;
}
body {
font-size: 12pt;
color: black;
}
table {
border-collapse: collapse;
}
}
@media (max-width: 768px) {
main {
margin-left: 0;
}
}
/* === Body & Container === */
body {
background-color: #e4dccc;
background-attachment: fixed;
}
.dashboard-container {
background-color: #ffffff;
box-shadow: 0 0 10px rgba(0,0,0,0.05);
border-radius: 8px;
padding: 20px;
margin: 30px auto;
max-width: 1200px;
}
.sidebar {
position: fixed;
width: 220px;
}
main {
margin-left: 220px;
}
/* === Sidebar === */
.sidebar {
background: linear-gradient(180deg, #b35400, #993d00); /* dégradé orange foncé */
color: white;
min-height: 100vh; /* prend toute la hauteur */
top: 0;
z-index: 1000;
overflow-y: auto;
padding: 1rem;
}
.sidebar .nav-link {
color: white;
font-weight: 700;
}
.sidebar .nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 5px;
color: white;
}
.sidebar .dropdown-menu {
display: none;
position: relative;
left: 10px;
top: -5px;
border: none;
padding-left: 1rem;
box-shadow: none;
}
.sidebar .dropdown:hover .dropdown-menu {
display: block;
}
.sidebar a {
color: white;
}
.sidebar a:hover {
color: white;
}
/* === Navbar / Logo / Profil === */
.logo {
max-width: 80px;
}
.img-profil {
width: 80px !important;
height: 80px !important;
object-fit: cover;
border-radius: 50%;
}
/* === Cards === */
.card {
border: 1px solid #dee2e6;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
border-radius: 8px;
transition: transform 0.3s ease;
}
.card:hover {
transform: scale(1.02);
}
.card-header {
background-color: #f8f9fa;
font-weight: bold !important;
color: black !important;
font-size: 1rem;
border-bottom: none;
}
.card-body h5 {
color: black !important;
font-weight: bold;
font-size: 2.2rem;
}
/* === Stat cards === */
.stat-card {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(113, 11, 11, 0.1);
}
/* === Employé / Projet details === */
.employe-detail {
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.employe-detail .card-title {
font-size: 1.8rem;
margin-bottom: 0.5rem;
}
.employe-detail p {
margin-bottom: 0.4rem;
font-size: 1rem;
}
.employe-detail a.btn {
font-weight: 500;
}
.employe-card, .projet-card {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
padding: 2.5rem;
max-width: 1400px;
margin: 0 auto;
font-size: 1.1rem;
}
/* === Forms === */
.form-wrapper {
max-width: 1000px;
margin: auto;
background-color: rgba(255, 255, 255, 0.95);
}
form label {
font-weight: 600;
margin-top: 1rem;
display: block;
}
form input, form select, form textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.3rem;
border-radius: 5px;
border: 1px solid #ccc;
}
/* === Formation badges === */
.formation-badge {
position: relative;
display: inline-block;
}
.formation-actions {
position: absolute;
top: -5px;
right: -5px;
display: none;
}
.formation-badge:hover .formation-actions {
display: inline-block;
}
.formation-actions button {
background: none;
border: none;
color: #fff;
margin-left: 5px;
font-size: 0.9rem;
}
/* === Context menu === */
#contextMenu {
width: 150px;
}
/* === Transitions / Animations === */
.fade-out {
opacity: 0;
transition: opacity 0.5s ease;
}
#main-content {
transition: opacity 0.3s ease;
}
.login-box {
background: linear-gradient(to right, #f5f7fa, #c3cfe2);
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
width: 100%;
max-width: 400px;
}
/* === Responsive === */
@media (max-width: 576px) {
.modal-dialog {
max-width: 100%;
margin: 0;
height: 100%;
}
.modal-content {
height: 100%;
border-radius: 0;
}
.modal-body {
overflow-y: auto;
}
.modal-footer button {
width: 100%;
}
.img-profil {
width: 50px;
height: 50px;
}
.sidebar {
width: 100%;
padding: 0.5rem 1rem;
}
.sidebar .nav-link {
font-size: 0.9rem;
padding: 0.5rem 1rem;
}
}