5 Commits

Author SHA1 Message Date
267994044c feature: Affichage de tous les detail de la resevation
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-06-08 11:06:22 +00:00
a4b4a68dd2 feature: Affichage du message sur l'tat du contrat 2026-05-11 13:13:08 +02:00
07dc097d27 feature: Affichage des details de la reservation 2026-05-11 13:12:42 +02:00
4146563f41 Fonctionnalite : Ajout de la liste des contrats 2026-05-11 13:05:06 +02:00
c0cdca48fa Fonctionnalite: Ajout des contrats 2026-05-11 12:43:16 +02:00
91 changed files with 734 additions and 1046 deletions

3
.gitignore vendored
View File

@@ -1,8 +1,7 @@
db.sqlite3
venv/ venv/
media/ media/
staticfiles/ staticfiles/
.env .env
migrations/ migrations/
__pycache__/
*.pyc *.pyc
db.sqlite3

5
Jenkinsfile vendored
View File

@@ -2,6 +2,7 @@ pipeline
{ {
agent any agent any
options { options {
// This is required if you want to clean before build
skipDefaultCheckout(true) skipDefaultCheckout(true)
} }
@@ -16,7 +17,7 @@ pipeline
{ {
steps steps
{ {
sh 'echo "Debut du pipeline test"' sh 'echo "Debut du pipeline"'
cleanWs() cleanWs()
checkout scm checkout scm
} }
@@ -38,8 +39,6 @@ pipeline
python manage.py makemigrations python manage.py makemigrations
python manage.py migrate python manage.py migrate
echo $SUDO_PASSWORD | sudo -S rm -r staticfiles
python manage.py collectstatic --noinput
echo $SUDO_PASSWORD | sudo -S chown -R www-data:www-data /var/www/sirh echo $SUDO_PASSWORD | sudo -S chown -R www-data:www-data /var/www/sirh
echo "Deploiement reussi" echo "Deploiement reussi"

Binary file not shown.

View File

@@ -12,7 +12,6 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
import os import os
from pathlib import Path from pathlib import Path
from decouple import config from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -27,8 +26,7 @@ SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool) DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',') ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
#CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
# Application definition # Application definition
@@ -42,9 +40,12 @@ INSTALLED_APPS = [
'gestion_employe', 'gestion_employe',
'gestion_conge', 'gestion_conge',
'gestion_projet', 'gestion_projet',
'gestion_salle' 'gestion_salle',
'simple_sso.sso_server'
] ]
LOGIN_URL = 'login'
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@@ -67,7 +68,6 @@ TEMPLATES = [
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'contexte_universel.contexte_universel',
], ],
}, },
}, },
@@ -79,14 +79,23 @@ WSGI_APPLICATION = 'SIRH.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases # https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
if config('ENVIRONMENT') == 'local':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
else:
DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': config("DB_NAME"), 'NAME': config('DATABASE_NAME'),
'USER': config("DB_USER"), 'USER': config('DATABASE_USER'),
'PASSWORD': config("DB_PASSWORD"), 'PASSWORD': config('DATABASE_PASSWORD'),
'HOST': config("DB_HOST"), 'HOST': config('DATABASE_HOST'),
'PORT': config("DB_PORT", cast=int), 'PORT': config('DATABASE_PORT'),
} }
} }
@@ -141,21 +150,12 @@ MEDIA_ROOT = BASE_DIR / "media"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Connexion sso
# LOGIN_URL = '/client/'
LOGIN_URL = 'simple-sso-login'
SSO_PRIVATE_KEY = config('SSO_PRIVATE_KEY')
SSO_PUBLIC_KEY = config('SSO_PUBLIC_KEY')
SSO_SERVER = config('SSO_SERVER')
#SSO_LOGOUT_URL = 'http://test-authentification.cerfig.org:89/deconnexion'
# Configuration de l'email # Configuration de l'email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'ssl0.ovh.net' EMAIL_HOST = 'ssl0.ovh.net'
EMAIL_PORT = 465 EMAIL_PORT = 465
EMAIL_USE_SSL = True EMAIL_USE_SSL = True
EMAIL_USE_TLS = False EMAIL_USE_TLS = False
EMAIL_HOST_USER = config("EMAIL_HOST_USER") EMAIL_HOST_USER = 'support.it@cerfig.org'
EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD") EMAIL_HOST_PASSWORD = 'Cerfig2025'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

View File

@@ -1,38 +0,0 @@
# SGL/sso.py
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from simple_sso.sso_client.client import Client
from gestion_employe.models import Departement, Employe
User = get_user_model()
class SIRHClient(Client):
def build_user(self, user_data):
"""Crée le User et son Profile avec les infos reçues du serveur."""
user_data_copy = user_data.copy()
user_data.pop("departement", None)
user_data.pop("chef_departement", None)
user_data.pop("groups", None)
user = super().build_user(user_data)
departement = None
if "departement" in user_data_copy:
departement = Departement.objects.get_or_create(
nom=user_data_copy.get("departement", ""))[0]
Employe.objects.update_or_create(
user = user,
defaults = {
"departement": departement,
"chef": user_data_copy.get("chef_departement", None),
}
)
for group_name in user_data_copy.get("groups", []):
group, _ = Group.objects.get_or_create(name=group_name)
user.groups.add(group)
return user

View File

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

View File

@@ -24,16 +24,15 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{{ next }}">
<div class="mb-3"> <div class="mb-3">
<label for="mail">Votre adresse email :</label> <label for="mail">Votre adresse email :</label>
<input type="text" name="mail" class="form-control" placeholder="Entrez votre e-mail" required> <input type="text" name="mail" class="form-control" placeholder="Entrez votre e-mail" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="mot_de_passe">Mot de passe</label> <label for="mot_de_passe">Mot de passe</label>
<input type="password" name="mot_de_passe" class="form-control" placeholder="Entrez votre mot de passe" required> <input type="password" name="mot_de_passe" class="form-control" placeholder="Entrez votre mot de passe" required>
<i class="bi bi-eye toggle-password" onclick="togglePassword()" <i class="bi bi-eye toggle-password" onclick="togglePassword()"
style="position:absolute; right:10px; top:38px; cursor:pointer;"></i> style="position:absolute; right:10px; top:38px; cursor:pointer;"></i>
</div> </div>
<button type="submit" class="btn btn-primary w-100">Se connecter</button> <button type="submit" class="btn btn-primary w-100">Se connecter</button>
</form> </form>

View File

@@ -1,24 +0,0 @@
{% load static %}
<nav class="navbar 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>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<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>

View File

@@ -13,36 +13,35 @@
<i class="bi bi-person-circle text-white" style="font-size:60px;"></i> <i class="bi bi-person-circle text-white" style="font-size:60px;"></i>
{% endif %} {% endif %}
<div class="text-white mt-2 fw-bold fs-5"> <div class="text-white mt-2 fw-bold fs-5">
{{ user.first_name }} {{ user.last_name }} {{ user.username }}
</div> </div>
</div> </div>
<a href="{% url 'gestion_employe:mon-profil' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;"> <a href="{% url 'gestion_employe:mon-profil' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-person-circle"></i> Mon profil <i class="bi bi-person-circle"></i> Mon profil
</a> </a>
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %} {% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
<a href="{% url 'gestion_employe:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;"> <a href="{% url 'gestion_employe:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-speedometer2"></i> Tableau de bord <i class="bi bi-speedometer2"></i> Tableau de bord
</a> </a>
{% endif %} {% endif %}
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %} {% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
<a href="{% url 'gestion_projet:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;"> <a href="{% url 'gestion_projet:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-folder"></i> Gestion des Projets <i class="bi bi-folder"></i> Gestion des Projets
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'gestion_conges:conge' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;"> <a href="{% url 'gestion_conges:conge' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-airplane"></i> Gestion des congés <i class="bi bi-airplane"></i> Gestion des congés
</a> </a>
{% if user|is_chef_projet %} {% if user|is_chef_projet %}
<a href="{% url 'gestion_projet:activites-projet' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;"> <a href="{% url 'gestion_projet:activites-projet' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-list-task"></i> Suivi des Activités <i class="bi bi-list-task"></i> Suivi des Activités
</a> </a>
{% endif %} {% endif %}
<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.4em">
<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.4em">
<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>
</div> </div>

View File

@@ -14,31 +14,34 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.conf.urls.static import static
from django.conf import settings
# from simple_sso.sso_server.server import Server
from . import views from . import views
from .sso import SIRHClient
client_sirh = SIRHClient( # server_sso = Server()
settings.SSO_SERVER,
settings.SSO_PUBLIC_KEY,
settings.SSO_PRIVATE_KEY
)
urlpatterns = [ urlpatterns = [
path(
'',
views.login_view,
name='index'
),
path('login/', path('login/',
views.connexion, views.login_view,
name='login' name='login'
), ),
path( path(
'deconnexion/', 'deconnexion/',
views.deconnexion, views.deconnexion_view,
name='deconnexion' name='deconnexion'
), ),
path( path(
'', 'employé/',
include("gestion_employe.urls") include("gestion_employe.urls")
), ),
path( path(
@@ -56,6 +59,9 @@ urlpatterns = [
path( path(
'admin/', 'admin/',
admin.site.urls admin.site.urls
), ),
path("client/", include(client_sirh.get_urls())), # path(
] # 'sso',
# include(server_sso.get_urls())
# )
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -1,13 +1,37 @@
from django.contrib.auth import logout from django.contrib.auth import authenticate, login, logout
from django.shortcuts import redirect from django.shortcuts import render, redirect
from django.urls import reverse_lazy from django.contrib import messages
def login_view(request):
def connexion(request): """
"""Redirige vers le serveur SSO pour l'authentification.""" Gère la connexion des utilisateurs avec redirection selon le rôle et
return redirect(reverse_lazy("simple-sso-login")) vérification de l'acceptation de la politique d'utilisation.
"""
def deconnexion(request): if request.method == 'POST':
"""Gérez la déconnexion des utilisateurs.""" email = request.POST.get('mail')
logout(request) password = request.POST.get('mot_de_passe')
return redirect("http://authentification.cerfig.org/login")
if not (email and password):
messages.error(request, "Veuillez remplir tous les champs.")
return render(request, 'login.html')
user = authenticate(request, username=email, password=password)
if user is None:
messages.error(request, "Nom d’utilisateur ou mot de passe incorrect.")
return render(request, 'login.html')
if not user.is_active:
messages.error(request, "Compte inactif. Contactez l'administrateur.")
return render(request, 'login.html')
login(request, user)
return redirect("gestion_conges:conge")
return render(request, 'login.html')
def deconnexion_view(request):
"""Gère la déconnexion de l'utilisateur."""
logout(request)
return redirect('login')

View File

@@ -1,7 +0,0 @@
from decouple import config
def contexte_universel(request):
return {
"SIRH_LIEN": config("SIRH_LIEN"),
"SGL_LIEN": config("SGL_LIEN")
}

View File

@@ -1,19 +1,13 @@
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_employe.models import Contrat, Employe from gestion_employe.models import Contrat, Employe
from gestion_conge.models import Conge
QUOTA_CONGE_ANNUEL = 30 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:
EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True))
except:
pass
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é"""
@@ -40,17 +34,10 @@ def solde_conge(employe):
"nombre_jours_valide": jours_conges_valider "nombre_jours_valide": jours_conges_valider
} }
def destinataire_mail_demande_conges(employe):
"""Fonction de récupération des destinataires pour les mails de demande de congés"""
if employe.chef:
return EMAIL_ASSISTANTE_DE_DIRECTION
else:
if employe.departement:
chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True)
return list(chefs_departement.values_list('user__email', flat=True))
def envoyer_mail(sujet, message, destinataires): def envoyer_mail(sujet, message, destinataires):
"""Fonction d'envoi de mail""" """Fonction d'envoi de mail"""
from django.core.mail import send_mail
from django.conf import settings
send_mail( send_mail(
sujet, sujet,
@@ -59,3 +46,12 @@ def envoyer_mail(sujet, message, destinataires):
destinataires, destinataires,
fail_silently=False, fail_silently=False,
) )
def destinataire_mail_demande_conges(employe):
"""Fonction de récupération des destinataires pour les mails de demande de congés"""
if employe.chef:
return EMAIL_ASSISTANTE_DE_DIRECTION
else:
if employe.departement:
chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True)
return list(chefs_departement.values_list('user__email', flat=True))

View File

@@ -1,13 +1,11 @@
from django import forms from django import forms
from .models import Conge from .models import Conge
class CongeForm(forms.ModelForm): class CongeForm(forms.ModelForm):
"""Formulaire de demande de congé.""" """Formulaire de demande de congé."""
class Meta: class Meta:
model = Conge model = Conge
fields =('type', 'date_debut', 'date_fin') fields =['type', 'date_debut', 'date_fin']
widgets = { widgets = {
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-09-03 14:33 # Generated by Django 5.2.13 on 2026-04-17 12:03
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,14 +1,19 @@
import pandas as pd import pandas as pd
from django.db import models from django.db import models
from gestion_employe.models import Employe from gestion_employe.models import Employe
class Conge(models.Model): class Conge(models.Model):
"""Modèle de création des congés""" """Modèle de création des congés"""
TYPE_CHOICES = ( TYPE_CHOICES = [
# ('maladie', 'Maladie'),
('conge_annuel', 'Conge Annuel'), ('conge_annuel', 'Conge Annuel'),
) # ('conge_maternite', 'Conge Maternité'),
# ('conge_mariage', 'Conge Mariage'),
# ('conge_naissance', 'Conge de Naissance'),
# ('conge_deces_proche', 'Conge de décès d\'un proche'),
# ('conge_mariage_proche', 'Conge de mariage d\'un proche'),
# ('autre', 'Autre'),
]
employe = models.ForeignKey( employe = models.ForeignKey(
Employe, Employe,

View File

@@ -3,6 +3,7 @@ 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", {
layout : "fitColumns",
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:{
@@ -14,7 +15,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"},
{"title": "Date de la demande", "field": "date_demande", responsive: 1}, {"title": "Date de la demande", "field": "date_demande"},
{"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 ,
}}, }},
@@ -23,7 +24,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}}, }},
], ],
pagination: true, pagination: true,
paginationSize: 5, paginationSize: 5
}) })
const bouton_demande_conges = $("bouton-demande-conge"); const bouton_demande_conges = $("bouton-demande-conge");

View File

@@ -7,7 +7,6 @@
<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 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">
@@ -33,17 +32,11 @@
<h5 class="fw-bold">{{ nombre_conges_valide }}</h5> <h5 class="fw-bold">{{ nombre_conges_valide }}</h5>
</div> </div>
</div> </div>
</div> <div class="d-flex justify-content-between my-4">
<div class="row mb-3">
<div class="col d-flex 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>
</div> <div class="table-responsive">
<div class="row">
<div class="col">
<div id="liste-demande-conges" data-url="{% url 'gestion_conges:liste-des-conges' %}"></div> <div id="liste-demande-conges" data-url="{% url 'gestion_conges:liste-des-conges' %}"></div>
</div> </div>
</div> </div>

View File

@@ -1,20 +1,16 @@
import json import json
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.forms.models import model_to_dict
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.utils import timezone from django.contrib import messages
from django.contrib.auth.decorators import login_required
from fonction_utilitaire import fonctions_utilitaire
from gestion_conge.forms import CongeForm from gestion_conge.forms import CongeForm
from gestion_employe.models import Affectation, Employe from gestion_employe.models import Affectation, Employe
from django.forms.models import model_to_dict
from django.utils import timezone
from django.db.models import Q
from fonction_utilitaire import fonctions_utilitaire
from .models import Conge from .models import Conge
@login_required @login_required
def index(request): def index(request):
"""Vue de gestion de l'index""" """Vue de gestion de l'index"""
@@ -119,9 +115,10 @@ def demander_conge(request):
conge_obj = form.save(commit=False) conge_obj = form.save(commit=False)
conge_obj.employe = employe conge_obj.employe = employe
if conge_obj.type == "conge_annuel" and retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel: if conge_obj.type == "conge_annuel":
messages.error(request, "Quota annuel dépassé (30 jours max).") if retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
return redirect("gestion_conges:conge") messages.error(request, "Quota annuel dépassé (30 jours max).")
return redirect("gestion_conges:conge")
conge_obj.save() conge_obj.save()
messages.success(request, "Votre demande de congé a été enregistrée.") messages.success(request, "Votre demande de congé a été enregistrée.")
@@ -129,7 +126,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) destinataires = fonctions_utilitaire.destinataire_mail_demande_conges() + [employe.user.email]
) )
return redirect("gestion_conges:conge") return redirect("gestion_conges:conge")
@@ -222,13 +219,13 @@ def validation_de_conge(request):
motif_refus = request_data.get("motif_refus", "") motif_refus = request_data.get("motif_refus", "")
if validation_hierarchique is not None: if validation_hierarchique is not None:
conge.validation_hierarchique = validation_hierarchique == "valide" conge.validation_hierarchique = True if validation_hierarchique == "valide" else False
if validation_hierarchique == "refuse" and not motif_refus: if validation_hierarchique == "refuse" and not motif_refus:
return JsonResponse({"message": "Veuillez fournir un motif de refus."}) return JsonResponse({"message": "Veuillez fournir un motif de refus."})
conge.motif_refus = motif_refus if validation_hierarchique == "refuse" else "" conge.motif_refus = motif_refus if validation_hierarchique == "refuse" else ""
if validation_direction is not None: if validation_direction is not None:
conge.validation_direction = validation_direction == "valide" conge.validation_direction = True if validation_direction == "valide" else False
if validation_direction == "refuse" and not motif_refus: if validation_direction == "refuse" and not motif_refus:
return JsonResponse({"message": "Veuillez fournir un motif de refus."}) return JsonResponse({"message": "Veuillez fournir un motif de refus."})
conge.motif_refus = motif_refus if validation_direction == "refuse" else "" conge.motif_refus = motif_refus if validation_direction == "refuse" else ""

View File

@@ -1,8 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import Departement, Employe from .models import Departement, Employe
@admin.register(Departement) @admin.register(Departement)
class DepartementAdmin(admin.ModelAdmin): class DepartementAdmin(admin.ModelAdmin):
list_display = ("nom", ) list_display = ("nom", )

View File

@@ -1,15 +1,11 @@
from django import forms from django import forms
from .models import Contrat, Departement, Employe,Affectation,Formation
from .models import Affectation, Contrat, Departement, Employe, Formation
class EmployeForm(forms.ModelForm): class EmployeForm(forms.ModelForm):
"""Formulaire pour modifier les informations de profil d'un employé""" """Formulaire pour modifier les informations de profil d'un employé"""
class Meta: class Meta:
model = Employe model = Employe
fields = ( fields = (
'departement',
'fonction',
'adresse', 'adresse',
'telephone', 'telephone',
'CV', 'CV',
@@ -17,18 +13,7 @@ class EmployeForm(forms.ModelForm):
'rib', 'rib',
'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:
@@ -52,7 +37,7 @@ class AffectationForm(forms.ModelForm):
class ContratForm(forms.ModelForm): class ContratForm(forms.ModelForm):
class Meta: class Meta:
model = Contrat model = Contrat
fields = ( fields = [
"numero_contrat", "numero_contrat",
"type_contrat", "type_contrat",
"date_debut", "date_debut",
@@ -60,7 +45,7 @@ class ContratForm(forms.ModelForm):
"salaire_mensuel", "salaire_mensuel",
"statut", "statut",
"fichier_contrat" "fichier_contrat"
) ]
widgets = { widgets = {
'numero_contrat': forms.TextInput(attrs={'class': 'form-control'}), 'numero_contrat': forms.TextInput(attrs={'class': 'form-control'}),
@@ -75,19 +60,19 @@ 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:
model = Formation model = Formation
fields = ( fields = [
'titre', 'titre',
'organisme', 'organisme',
'date_obtention', 'date_obtention',
'date_fin', 'date_fin',
'description', 'description',
'certificat' 'certificat'
) ]
widgets = { widgets = {
'titre': forms.TextInput(attrs={"class": "form-control"}), 'titre': forms.TextInput(attrs={"class": "form-control"}),

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-09-03 14:33 # Generated by Django 5.2.13 on 2026-04-17 12:03
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings from django.conf import settings
@@ -27,7 +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)),
('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)),
('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)),
@@ -48,7 +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)),
('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)),
('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

@@ -0,0 +1,18 @@
# 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

@@ -1,10 +1,8 @@
from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone from django.utils import timezone
from gestion_projet.models import Projet from gestion_projet.models import Projet
class Departement(models.Model): class Departement(models.Model):
"""Modèle représentant un département de l'entreprise.""" """Modèle représentant un département de l'entreprise."""
nom = models.CharField(max_length=100) nom = models.CharField(max_length=100)
@@ -14,7 +12,7 @@ class Departement(models.Model):
class Employe(models.Model): class Employe(models.Model):
"""Modèle représentant un employé de l'entreprise.""" """Modèle représentant un employé de l'entreprise."""
FONCTION_LISTE = ( FONCTION_LISTE = [
('directeur', 'Directeur'), ('directeur', 'Directeur'),
('assistant_direction', 'Assistante de direction'), ('assistant_direction', 'Assistante de direction'),
('assistant_technique_recherche', 'Assistant technique de recherche'), ('assistant_technique_recherche', 'Assistant technique de recherche'),
@@ -26,7 +24,7 @@ class Employe(models.Model):
('qualiticien', 'Qualiticien'), ('qualiticien', 'Qualiticien'),
('technicien_surface', 'Technicien de surface'), ('technicien_surface', 'Technicien de surface'),
('chauffeur', 'Chauffeur'), ('chauffeur', 'Chauffeur'),
) ]
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
matricule = models.CharField(max_length=10, unique=True, null=True, blank=True) matricule = models.CharField(max_length=10, unique=True, null=True, blank=True)
@@ -54,21 +52,20 @@ class Employe(models.Model):
class Contrat(models.Model): class Contrat(models.Model):
"""Modèle représentant un contrat de travail d'un employé.""" """Modèle représentant un contrat de travail d'un employé."""
TYPE_CONTRAT = ( TYPE_CONTRAT = [
('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_determinee', 'Contrat à Durée Déterminée'),
('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'),
('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_prestation', 'Contrat de Prestation de Service'),
('contrat_stage', 'Contrat de Stage'), ('contrat_stage', 'Contrat de Stage'),
('convention_bourse_entretien', 'Convention de bourse d\'entretien'), ('convention_bourse_entretien', 'Convention de bourse d\'entretien'),
) ]
STATUT_CONTRAT = ( STATUT_CONTRAT = [
('actif', 'Actif'), ('actif', 'Actif'),
('termine', 'Terminé'), ('termine', 'Terminé'),
('suspendu', 'Suspendu'), ('suspendu', 'Suspendu'),
('rupture_contrat', 'Rupture de Contrat'), ('rupture_contrat', 'Rupture de Contrat'),
) ]
employe = models.ForeignKey(Employe, on_delete=models.CASCADE) employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
numero_contrat = models.CharField(max_length=100, unique=True) numero_contrat = models.CharField(max_length=100, unique=True)
type_contrat = models.CharField( type_contrat = models.CharField(
@@ -92,7 +89,7 @@ class Contrat(models.Model):
class Affectation(models.Model): class Affectation(models.Model):
"""Modèle représentant l'affectation d'un employé à un projet avec un rôle spécifique.""" """Modèle représentant l'affectation d'un employé à un projet avec un rôle spécifique."""
ROLE_CHOICES = ( ROLE_CHOICES = [
('chef_projet', 'Chef de projet'), ('chef_projet', 'Chef de projet'),
('doctorant','Doctorant'), ('doctorant','Doctorant'),
('mastorant','Mastorant'), ('mastorant','Mastorant'),
@@ -101,7 +98,7 @@ class Affectation(models.Model):
('laborantin','Laborantin'), ('laborantin','Laborantin'),
('medecin','Médecin'), ('medecin','Médecin'),
('autre','Autre'), ('autre','Autre'),
) ]
employe = models.ForeignKey(Employe, on_delete=models.CASCADE) employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
projet = models.ForeignKey(Projet, on_delete=models.CASCADE) projet = models.ForeignKey(Projet, on_delete=models.CASCADE)

View File

@@ -12,7 +12,6 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe, // ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
responsiveLayout : "hide"
}) })
fetch(url_liste_employe) fetch(url_liste_employe)
@@ -29,8 +28,6 @@ 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();
console.log('Employé cliqué:', data);
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;
@@ -213,41 +210,23 @@ $("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;
console.log('=== ENREGISTREMENT DÉTAILS ===');
console.log('ID:', id_);
console.log('Fonction:', fonction);
console.log('Date embauche:', dateEmbauche);
console.log('Matricule:', matricule);
console.log('Département (valeur brute):', departement);
console.log('Département (type):', typeof departement);
console.log('Département est vide:', !departement);
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url; const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
const payload = {
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule,
"departement": departement
};
console.log('Payload JSON:', payload);
console.log('Payload stringifié:', JSON.stringify(payload));
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(payload) body: JSON.stringify({
"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();
}) })
@@ -281,73 +260,4 @@ 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;
console.log("Employe ID :", employeId);
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,20 +13,18 @@ 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 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);
@@ -34,10 +32,6 @@ 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",

View File

@@ -18,21 +18,16 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if not expiration_contrat %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
Les informations sur votre contrat n'ont pas été renseignées,
veuillez contacter les ressources humaines. </div>
{% elif contrat_nb_jours_restant > 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong> {% if not has_contrat %}
Votre contrat de travail expire dans <strong>{{ contrat_nb_jours_restant }} jour{{ contrat_nb_jours_restant|pluralize }}</strong>, <div class="alert alert-danger mt-2">
veuillez contacter les ressources humaines. </div> <strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines.
</div>
{% elif contrat_nb_jours_restant == 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong> {% elif expiration_contrat %}
Votre contrat de travail <strong>expire aujourd'hui</strong>. <div class="alert alert-warning mt-2">
Veuillez contacter les ressources humaines. </div> <strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
</div>
{% elif contrat_nb_jours_restant < 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
Votre contrat de travail est <strong>déjà expiré</strong>.
Veuillez contacter les ressources humaines. </div>
{% endif %} {% endif %}
<div class="accordion mt-2" id="accordionInformationEmploye"> <div class="accordion mt-2" id="accordionInformationEmploye">
@@ -44,7 +39,7 @@ Veuillez contacter les ressources humaines. </div>
</h2> </h2>
<div id="collapseProfil" class="accordion-collapse" aria-labelledby="headingContrats" data-bs-parent="#accordionInformationEmploye"> <div id="collapseProfil" class="accordion-collapse" aria-labelledby="headingContrats" data-bs-parent="#accordionInformationEmploye">
<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' %}"> <div class="row" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
{% csrf_token %} {% csrf_token %}
<div class="col"> <div class="col">
<div class="form-group mb-2"> <div class="form-group mb-2">
@@ -56,19 +51,19 @@ Veuillez contacter les ressources humaines. </div>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Matricule :</label> <label>Matricule :</label>
<input type="text" class="form-control" id="matricule" name = 'matricule' value="{{ employe.matricule|default:'' }}" readonly> <input type="text" class="form-control" id="matricule" value="{{ employe.matricule|default:'' }}" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Nom :</label> <label>Nom :</label>
<input type="text" class="form-control" id="nom" name = 'nom' value="{{ employe.user.last_name }}"> <input type="text" class="form-control" id="nom" value="{{ employe.user.last_name }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Prénom :</label> <label>Prénom :</label>
<input type="text" class="form-control" id="prenom" name = 'prenom' value="{{ employe.user.first_name }}"> <input type="text" class="form-control" id="prenom" value="{{ employe.user.first_name }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Sexe :</label> <label>Sexe :</label>
<select id='sexe' name = 'sexe' class="form-select"> <select id='sexe' class="form-select">
<option value='h' {% if employe.sexe == 'h' %}selected{% endif %}>Homme</option> <option value='h' {% if employe.sexe == 'h' %}selected{% endif %}>Homme</option>
<option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option> <option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option>
</select> </select>
@@ -78,27 +73,27 @@ Veuillez contacter les ressources humaines. </div>
<div class="col"> <div class="col">
<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" value="{{ employe.date_naissance|date:'Y-m-d' }}">
</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="departement" name = 'departement' value="{{ employe.departement.nom|default:'' }}" readonly> <input type="text" class="form-control" id="departement" value="{{ employe.departement.nom|default:'' }}" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Email :</label> <label>Email :</label>
<input type="email" class="form-control" id="email" name = 'email' value="{{ employe.user.email|default:'' }}"> <input type="email" class="form-control" id="email" value="{{ employe.user.email|default:'' }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Téléphone :</label> <label>Téléphone :</label>
<input type="text" class="form-control" id="telephone" name = 'telephone' value="{{ employe.telephone|default:'' }}"> <input type="text" class="form-control" id="telephone" value="{{ employe.telephone|default:'' }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Adresse :</label> <label>Adresse :</label>
<input type="text" class="form-control" id="adresse" name = 'adresse' value="{{ employe.adresse|default:'' }}"> <input type="text" class="form-control" id="adresse" value="{{ employe.adresse|default:'' }}">
</div> </div>
</div> </div>
<button type="submit" class="btn btn-success d-block m-auto" id="enregistrerProfil">Enregistrer</button> </div>
</form> <button type="submit" class="btn btn-success d-block m-auto" id="enregistrerProfil">Enregistrer</button>
</div> </div>
</div> </div>
</div> </div>
@@ -132,7 +127,7 @@ Veuillez contacter les ressources humaines. </div>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Statut :</label> <label>Statut :</label>
<input type="text" class="form-control" value="{{ contrat.statut }}" id="statut" readonly> <input type="text" class="form-control" value="{{ contrat.statut }}" id="satut" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>fichier :</label> <label>fichier :</label>

View File

@@ -1,17 +1,24 @@
<div class="modal fade" id="modalContratsEmploye" tabindex="-1"> <div class="modal fade" id="modalContratsEmploye" tabindex="-1">
<div class="modal-dialog modal-xl"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"> <h5 class="modal-title">📄 Contrats de l'employé</h5>
Contrats de <span id="nom-employe-contrat"></span> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</h5> </div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> <div class="modal-body">
<div class="modal-body"> <div id="contrats-employe-body">
<div id="table-contrats-tabulator"></div> <!-- contenu dynamique JS -->
</div>
</div> </div>
</div> </div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">
Fermer
</button>
</div>
</div>
</div>
</div>

View File

@@ -8,7 +8,7 @@
</h5> </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" id="modalDetailEmployeBody"> <div class="modal-body" id="modalDetailEmployeBody" data-url="{% url 'gestion_employe:enregistrer-detail-employe' %}">
<div class="row g-3"> <div class="row g-3">
<input type="hidden" id="detail-id"> <input type="hidden" id="detail-id">
<div class="col-md-6"> <div class="col-md-6">
@@ -23,12 +23,7 @@
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Département :</label> <label>Département :</label>
<select class="form-select" id="detail-departement"> <input type="text" class="form-control" 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>
@@ -98,6 +93,9 @@
</h5> </h5>
{% if user|has_group:"ressource_humaine" %} {% if user|has_group:"ressource_humaine" %}
<div> <div>
<button class="btn btn-secondary" disabled>
<i class="bi bi-file-earmark-lock"></i> Contrat actif
</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' %}" id="formulaire_mot_de_passe"> <form method="POST" action="{% url 'gestion_employe:modifier-mot-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

@@ -6,7 +6,6 @@ la gestion de l'accès des utilisateurs aux différentes pages.
from django import template from django import template
from django.utils import timezone from django.utils import timezone
from gestion_employe.models import Affectation from gestion_employe.models import Affectation
register = template.Library() register = template.Library()

View File

@@ -1,6 +1,5 @@
from django.urls import path
from . import views from . import views
from django.urls import path
app_name = 'gestion_employe' app_name = 'gestion_employe'
@@ -30,17 +29,11 @@ urlpatterns = [
views.creation_contrat, views.creation_contrat,
name='creation-contrat' name='creation-contrat'
), ),
path(
'employe/<int:employe_id>/contrats/'
, views.contrats_employe,
name="contrats-employe"
),
path( path(
'contrat/supprimer/', 'contrat/supprimer/',
views.suppression_contrat, views.suppression_contrat,
name='supprimer-contrat' name='supprimer-contrat'
), ),
path( path(
'Affectation/affectation/', 'Affectation/affectation/',
views.affecter_employe_projet, views.affecter_employe_projet,

View File

@@ -1,32 +1,25 @@
import json import json
from datetime import datetime, timedelta from datetime import date, timedelta, datetime
import requests
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.contrib import messages
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
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.forms.models import model_to_dict
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone from django.utils import timezone
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from fonction_utilitaire import fonctions_utilitaire from django.http import JsonResponse
from django.forms.models import model_to_dict
from django.db.models import Sum
from .models import Employe, Contrat, Affectation, Formation
from .forms import AffectationForm, ContratForm, FormationForm from .forms import AffectationForm, ContratForm, FormationForm
from .models import Affectation, Contrat, Departement, Employe, Formation from fonction_utilitaire import fonctions_utilitaire
@login_required @login_required
def index(request): def index(request):
"""Vue d'index""" """Vue d'index"""
if not request.user.groups.filter(name="ressource_humaine").exists():
return redirect('gestion_employe:mon-profil')
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()
@@ -45,8 +38,7 @@ 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
@@ -102,7 +94,7 @@ def affecter_employe_projet(request):
elif total_affectation + temps_nouveau > 100: elif total_affectation + temps_nouveau > 100:
messages.warning( messages.warning(
request, request,
f"Les pourcentages d'affectation de l'employé {employe.user.first_name} {employe.user.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)." f"Les pourcentages d'affectation de l'employé {employe.first_name} {employe.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
) )
return redirect('gestion_employe:index') return redirect('gestion_employe:index')
@@ -150,6 +142,15 @@ 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',
@@ -174,11 +175,30 @@ def mon_profil(request):
], ],
"formation_form": FormationForm(), "formation_form": FormationForm(),
"has_contrat": contrats is not None, "has_contrat": has_contrat,
"expiration_contrat": contrats.nombre_jours_restant <= fonctions_utilitaire.DUREE_FIN_CONTRAT if contrats else False, "expiration_contrat": expiration_contrat,
"contrat_nb_jours_restant": max(0, contrats.nombre_jours_restant if contrats else 0) "contrat_nb_jours_restant": contrat_nb_jours_restant
} }
) )
@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é"""
@@ -187,25 +207,25 @@ def modifier_employer(request):
except Employe.DoesNotExist: except Employe.DoesNotExist:
return JsonResponse({"message": "Employé non trouvé."}) return JsonResponse({"message": "Employé non trouvé."})
if request.method == "POST": if request.method == "POST":
data = json.loads(request.body)
user = User.objects.get(username=request.user) user = User.objects.get(username=request.user)
user.last_name = request.POST['nom'] user.last_name = data['nom']
user.first_name = request.POST['prenom'] user.first_name = data['prenom']
user.email = request.POST['email'] user.email = data['email']
employe.telephone = request.POST['telephone'] employe.telephone = data['telephone']
employe.adresse = request.POST['adresse'] employe.adresse = data['adresse']
employe.sexe = request.POST['sexe'] employe.sexe = data['sexe']
if request.FILES.get("photo"): if request.FILES.get("photo"):
employe.photo = request.FILES["photo"] employe.photo = request.FILES["photo"]
if request.POST['date_naissance']: if data['date_naissance']:
difference = relativedelta(timezone.now().date(), datetime.strptime(request.POST['date_naissance'], "%Y-%m-%d").date()) difference = relativedelta(timezone.now().date(), datetime.strptime(data['date_naissance'], "%Y-%m-%d").date())
if difference.years >= 18: if difference.years >= 18:
employe.date_naissance = request.POST['date_naissance'] employe.date_naissance = data['date_naissance']
else: else:
messages.error(request, "Veuillez entrez une date de naissance correcte.") return JsonResponse({"message": "Veuillez entrez une date de naissance correcte."})
employe.save() employe.save()
user.save() user.save()
messages.success(request, "Profil mis à jour avec succès.") return JsonResponse({"message": "Profil mis à jour avec succès."})
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"""
@@ -260,7 +280,7 @@ def creation_contrat(request):
contrat_actif = Contrat.objects.filter( contrat_actif = Contrat.objects.filter(
employe=employe, employe=employe,
date_fin__gte=timezone.now().date() date_fin__gte=date.today()
).exists() ).exists()
if request.method == "POST": if request.method == "POST":
@@ -286,45 +306,6 @@ def creation_contrat(request):
'contrat_form': form 'contrat_form': form
}) })
@login_required
def contrats_employe(request, employe_id):
try:
employe = Employe.objects.get(id=employe_id)
except Employe.DoesNotExist:
return JsonResponse(
{"error": "Employé non trouvé"},
status=404
)
contrats = Contrat.objects.filter(
employe=employe
).order_by("-date_debut")
data = [
{
"id": contrat.id,
"numero_contrat": contrat.numero_contrat,
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(
contrat.type_contrat
),
"date_debut": contrat.date_debut.strftime("%Y-%m-%d"),
"date_fin": contrat.date_fin.strftime("%Y-%m-%d") if contrat.date_fin else "",
"salaire_mensuel": contrat.salaire_mensuel,
"statut": dict(Contrat.STATUT_CONTRAT).get(
contrat.statut
),
"fichier": contrat.fichier_contrat.url if contrat.fichier_contrat else "",
}
for contrat in contrats
]
return JsonResponse({
"employe": employe.user.get_full_name(),
"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"""
@@ -335,30 +316,10 @@ 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.get('fonction') employe.fonction = data['fonction']
employe.date_embauche = data.get('date_embauche') employe.date_embauche = data['date_embauche']
employe.matricule = data.get('matricule') employe.matricule = data['matricule']
# Traiter le département
dept_value = data.get('departement')
print(f"[DEBUG] Département brut: {dept_value!r}")
print(f"[DEBUG] Type département: {type(dept_value).__name__}")
print(f"[DEBUG] Est vide: {not dept_value}")
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)
@@ -420,7 +381,7 @@ def liste_employe(request):
"affectations": affectations, "affectations": affectations,
"projet": projets, "projet": projets,
"contrats": contrats, "contrats": contrats,
"departement": employe.departement.id if employe.departement else "", "departement": employe.departement.nom 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,
@@ -492,47 +453,3 @@ 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(
"http://localhost:8000/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

@@ -1,16 +1,14 @@
from django import forms from django import forms
from gestion_projet.models import Projet from gestion_projet.models import Projet
from .models import ( from .models import (
ActiviteProjet, ActiviteProjet,
Bailleur, Bailleur,
DocumentProjet, DocumentProjet,
FinancementProjet, FinancementProjet,
LivrablesLivres, LivrablesLivres,
DomaineDeRecherche
) )
class ProjetForm(forms.ModelForm): class ProjetForm(forms.ModelForm):
"""Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs.""" """Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs."""
class Meta: class Meta:

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-09-03 14:33 # Generated by Django 5.2.13 on 2026-04-17 12:03
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,9 +1,7 @@
from datetime import datetime
from django.db import models from django.db import models
from datetime import date
from django.utils import timezone from django.utils import timezone
class Bailleur(models.Model): class Bailleur(models.Model):
"""Modèle représentant un bailleur de fonds pour les projets de recherche.""" """Modèle représentant un bailleur de fonds pour les projets de recherche."""
nom_organisme = models.CharField( nom_organisme = models.CharField(
@@ -31,12 +29,12 @@ class Bailleur(models.Model):
class DomaineDeRecherche(models.Model): class DomaineDeRecherche(models.Model):
"""Modèle représentant les domaines de recherche""" """Modèle représentant les domaines de recherche"""
DOMAINE_RECHERCHE = ( DOMAINE_RECHERCHE = [
('sciences_sociales', 'Sciences sociales'), ('sciences_sociales', 'Sciences sociales'),
('naturelles', 'Naturelles'), ('naturelles', 'Naturelles'),
('humaines', 'Humaines'), ('humaines', 'Humaines'),
('veterinaires', 'Vétérinaires') ('veterinaires', 'Vétérinaires')
) ]
nom = models.CharField( nom = models.CharField(
max_length=100, max_length=100,
@@ -54,14 +52,13 @@ 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 = ( TYPE_PROJET = [
('laboratoire', 'Laboratoire'), ('laboratoire', 'Laboratoire'),
('épidémiologie', 'Épidémiologie'), ('épidémiologie', 'Épidémiologie'),
('sciences sociales', 'Sciences sociales'), ('sciences sociales', 'Sciences sociales'),
('cliniques', 'Cliniques'), ('cliniques', 'Cliniques'),
('autre', 'Autre'), ('autre', 'Autre'),
) ]
id_projet = models.CharField( id_projet = models.CharField(
max_length=100, max_length=100,
blank=True, blank=True,
@@ -114,13 +111,13 @@ class Projet(models.Model):
@property @property
def statut(self): def statut(self):
if self.date_fin < datetime.date.today(): if self.date_fin < date.today():
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 = date.today()
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
@@ -158,7 +155,7 @@ class FinancementProjet(models.Model):
class DocumentProjet(models.Model): class DocumentProjet(models.Model):
"""Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché.""" """Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché."""
NOM_DOCUMENT_CHOICES = ( NOM_DOCUMENT_CHOICES = [
('protocole', 'Protocole d’étude'), ('protocole', 'Protocole d’étude'),
('ethique', "Approbation du comité d'éthique"), ('ethique', "Approbation du comité d'éthique"),
('autorisation', 'Autorisation (DNLP)'), ('autorisation', 'Autorisation (DNLP)'),
@@ -168,7 +165,7 @@ class DocumentProjet(models.Model):
('convention', 'Convention'), ('convention', 'Convention'),
('rapport_final', 'Rapport final'), ('rapport_final', 'Rapport final'),
('autre', 'Autre'), ('autre', 'Autre'),
) ]
projet = models.ForeignKey( projet = models.ForeignKey(
Projet, Projet,

View File

@@ -44,4 +44,4 @@ table = new Tabulator("#table-bailleurs", {
supprimerBailleur(data.id); supprimerBailleur(data.id);
} }
} }
}); });

View File

@@ -48,4 +48,3 @@
</div> </div>
</div> </div>
</div> </div>

View File

@@ -90,6 +90,12 @@ urlpatterns = [
views.liste_activites_projet, views.liste_activites_projet,
name='liste-activites-projet' name='liste-activites-projet'
), ),
# path(
# 'projet/ajout-de-document/',
# views.ajouter_document_projet,
# name='ajouter-document'
# ),
path( path(
'projet/liste-des-documents/', 'projet/liste-des-documents/',
views.liste_documents_projet, views.liste_documents_projet,

View File

@@ -1,34 +1,31 @@
import datetime from datetime import date
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
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 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.shortcuts import redirect, render
from django.utils import timezone
from gestion_employe.forms import AffectationForm from gestion_employe.forms import AffectationForm
from gestion_employe.models import Affectation, Employe from gestion_employe.models import Affectation, Employe
from gestion_projet.forms import ProjetForm from gestion_projet.forms import ProjetForm
from gestion_projet.models import Projet from gestion_projet.models import Projet
from .forms import (
ActiviteProjetForm,
BailleurForm,
DocumentProjetForm,
FinancementProjetFrom,
LivrablesLivresForm,
)
from .models import ( from .models import (
DocumentProjet,
Bailleur,
FinancementProjet,
ActiviteProjet, ActiviteProjet,
Bailleur,
DocumentProjet,
FinancementProjet,
LivrablesLivres, LivrablesLivres,
) )
from .forms import (
ActiviteProjetForm,
DocumentProjetForm,
FinancementProjetFrom,
BailleurForm,
LivrablesLivresForm
)
def liste_projet(request): def liste_projet(request):
""" Vue pour retourner la liste de tous les projet """ """ Vue pour retourner la liste de tous les projet """
@@ -92,7 +89,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.datetime.now(tz=datetime.timezone.utc).date()).count(), 'projet_en_cours': Projet.objects.filter(date_fin__gte=date.today()).count(),
} }
return render(request, 'gestion_projet/index.html', context) return render(request, 'gestion_projet/index.html', context)
@@ -141,9 +138,10 @@ def mises_a_jour_projet(request):
@login_required @login_required
def creation_bailleur(request): def creation_bailleur(request):
form = BailleurForm(request.POST) form = BailleurForm(request.POST)
if request.method == 'POST' and form.is_valid(): if request.method == 'POST':
form.save() if form.is_valid():
return JsonResponse({'success': True}) form.save()
return JsonResponse({'success': True})
return JsonResponse({'success': False}) return JsonResponse({'success': False})
@login_required @login_required
@@ -337,7 +335,6 @@ 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,12 +1,10 @@
from django import forms from django import forms
from .models import Reservation from .models import Reservation
class ReservationForm(forms.ModelForm): class ReservationForm(forms.ModelForm):
class Meta: class Meta:
model = Reservation model = Reservation
fields = ('salle', 'date_debut', 'date_fin', 'heure_debut', 'heure_fin', 'motif_reservation', 'besoin_zoom', 'besoin_ordi') fields = ['salle', 'date_debut', 'date_fin', 'heure_debut', 'heure_fin', 'motif_reservation', 'besoin_zoom', 'besoin_ordi']
widgets = { widgets = {
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-09-03 14:33 # Generated by Django 5.2.13 on 2026-04-17 12:03
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,21 +1,20 @@
from django.db import models from django.db import models
from gestion_employe.models import Employe from gestion_employe.models import Employe
class Reservation(models.Model): class Reservation(models.Model):
"""Modèle de création des réservations""" """Modèle de création des réservations"""
TYPE_CHOICES = ( TYPE_CHOICES = [
('formation', 'Salle de formation'), ('formation', 'Salle de formation'),
('reunion', 'Salle de réunion'), ('reunion', 'Salle de réunion'),
('lien_zoom', 'Lien Zoom'), ('lien_zoom', 'Lien Zoom'),
) ]
STATUT = ( STATUT = [
('en_attente', 'En attente'), ('en_attente', 'En attente'),
('validee', 'Validée'), ('validee', 'Validée'),
('refusee', 'Refusée'), ('refusee', 'Refusée'),
('annulee', 'Annulée'), ('annulee', 'Annulée'),
) ]
employe = models.ForeignKey(Employe, on_delete=models.CASCADE) employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
salle = models.CharField(max_length=100, choices=TYPE_CHOICES) salle = models.CharField(max_length=100, choices=TYPE_CHOICES)

View File

@@ -12,12 +12,7 @@ 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 => { .then(response => response.json())
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;
@@ -44,8 +39,12 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("lien_zoom_container").className = "d-none"; $("lien_zoom_container").className = "d-none";
} }
if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
}
}) })
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
} }
}); });
@@ -87,16 +86,9 @@ 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;
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault();
fetch( fetch(
url_annuler, url_annuler,
{ {
@@ -143,15 +135,10 @@ 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;
console.log(idRes);
e.preventDefault();
fetch(url, { fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@@ -171,7 +158,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_evenement"}, {title: "Date de l'evenement", field: "date_debut"},
{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"},
@@ -189,8 +176,6 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
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;
@@ -198,7 +183,9 @@ 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_evenement").value = data.date_evenement; $("date_debut").value = data.date_debut;
$("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;
@@ -206,20 +193,6 @@ 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;
console.log("Verification de data :", data);
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){
$("bouton-annuler").className = "d-none";
}
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide(); bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
modal.show(); modal.show();

View File

@@ -9,14 +9,14 @@
{% 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" 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>
{% endfor %} {% endfor %}
{% 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">Réservation de salle</h1> <h1 class="text-center">Reservation de salle</h1>
<hr> <hr>
<div class="row d-flex justify-content-around"> <div class="row d-flex justify-content-around">
<div class="form-group col-5 me-2"> <div class="form-group col-5 me-2">
@@ -26,15 +26,15 @@
<div class="form-group col-5 me-2"> <div class="form-group col-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='formation'>Salle de formation</option>
<option value='Salle de réunion'>Salle de réunion</option> <option value='reunion'>Salle de réunion</option>
<option value='Lien Zoom'>Lien Zoom</option> <option value='lien_zoom'>Lien Zoom</option>
</select> </select>
</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 justify-content-center">
<button class="btn btn-lg btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle réservation</button> <button class="btn btn-lg btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle reservation</button>
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#modalReservationAttente"> <button type="button" class="btn 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>
@@ -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' %}?v=3"></script> <script src="{% static 'gestion_salle/js/index.js' %}"></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 réservation Nouvelle reservation
</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

@@ -1,18 +1,17 @@
<!-- Modal d'affichage des détails d'une reservation --> <!-- Modal d'affichage des détails d'une reservation -->
{% load tags_personnaliser %}
<div class="modal fade" id="modalDetailReservation" tabindex="-1" aria-labelledby="modalDetailReservationLabel" aria-hidden="true"> <div class="modal fade" id="modalDetailReservation" tabindex="-1" aria-labelledby="modalDetailReservationLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<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 réservation (<span id='statut-reservation'></span>) Détails de la reservation (<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' hidden> <input type='hidden' class="form-control" id="id_reservation_detail" name='id_reservation'>
<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,7 +21,7 @@
<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 l'évènement :</label> <label>Date de debut :</label>
<input type='date' class="form-control" id="date_evenement" readonly > <input type='date' class="form-control" id="date_evenement" readonly >
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
@@ -34,7 +33,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 réservation :</label> <label>Motif de la reservation :</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">
@@ -46,20 +45,19 @@
<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 {{ reservation.statut }}</label> <label label="form-check-label">Besoin d'un ordinateur</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>
<hr />
<div class='d-flex justify-content-around mt-2'> <div class='d-flex justify-content-around mt-2'>
{% if appartient_au_departement_informatique %} {% if appartient_au_departement_informatique %}
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button> <button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button>
{% endif %} {% endif %}
{% if appartient_direction %} {% if appartient_direction and reservation.statut == "en_attente" %}
<button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button> <button class="btn btn-danger" 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" id="bouton-valider">Valider</button>
{% endif %} {% endif %}
<span id="current-user-id" data-user-id="{{ request.user.id }}"></span> <span id="current-user-id" data-user-id="{{ request.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>
</div> </div>

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 réservation en attente de validation Liste des reservation 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

@@ -1,5 +1,4 @@
from django.urls import path from django.urls import path
from . import views from . import views
app_name = "gestion_salle" app_name = "gestion_salle"

View File

@@ -1,17 +1,16 @@
import json
from datetime import timedelta 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.forms import model_to_dict
from django.http import HttpRequest, JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.http import JsonResponse, HttpRequest
from django.forms import model_to_dict
from fonction_utilitaire import fonctions_utilitaire from fonction_utilitaire import fonctions_utilitaire
from gestion_employe.models import Employe from gestion_employe.models import Employe
from gestion_salle.forms import ReservationForm from gestion_salle.forms import ReservationForm
from .models import Reservation from .models import Reservation
from datetime import timedelta
from django.contrib.auth.models import User
@login_required @login_required
@@ -23,6 +22,8 @@ def index(request: HttpRequest):
messages.error(request, "Profil employé introuvable.") messages.error(request, "Profil employé introuvable.")
return redirect('gestion_conges:conge') return redirect('gestion_conges:conge')
if request.method == "POST": if request.method == "POST":
form = ReservationForm(request.POST) form = ReservationForm(request.POST)
@@ -73,20 +74,21 @@ def index(request: HttpRequest):
current_date += timedelta(days=1) current_date += timedelta(days=1)
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: if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
fonctions_utilitaire.envoyer_mail( fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle", sujet = "Reservation de salle",
message = f""" message = f"""
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}".
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 = list(fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION)
) )
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
formulaire_reservation = ReservationForm() formulaire_reservation = ReservationForm()
departement = employe.departement departement = employe.departement
appartient_direction = request.user.groups.filter(name='direction').exists()
liste_demande_reservation = Reservation.objects.filter( liste_demande_reservation = Reservation.objects.filter(
employe=employe, employe=employe,
statut='en_attente' statut='en_attente'
@@ -96,19 +98,16 @@ Veuillez vous connecter à la plateforme pour plus de détails.""",
'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': departement and departement.nom == "Systeme informatique",
'appartient_direction': request.user.groups.filter(name='direction').exists(), 'appartient_direction': appartient_direction,
'liste_demande_reservation': list(liste_demande_reservation), 'liste_demande_reservation': list(liste_demande_reservation),
} }
return render(request, "gestion_salle/index.html", context) return render(request, "gestion_salle/index.html", context)
def liste_reservation(request:HttpRequest): 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"
@@ -117,8 +116,6 @@ def liste_reservation(request:HttpRequest):
else: else:
color = "#dc3545" color = "#dc3545"
print(dict(Reservation.TYPE_CHOICES).get(reservation.salle))
liste_reservation.append({ liste_reservation.append({
"guid": reservation.pk, "guid": reservation.pk,
"title": dict(Reservation.TYPE_CHOICES).get(reservation.salle), "title": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
@@ -136,23 +133,22 @@ def liste_reservation_attente(request):
{ {
**model_to_dict(reservation), **model_to_dict(reservation),
"employe": f"{reservation.employe.user.first_name} {reservation.employe.user.last_name}", "employe": f"{reservation.employe.user.first_name} {reservation.employe.user.last_name}",
"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),
"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': dict(Reservation.TYPE_CHOICES).get(reservation.salle), 'salle': 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'),
@@ -162,7 +158,6 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
'besoin_ordinateur': reservation.besoin_ordi, 'besoin_ordinateur': reservation.besoin_ordi,
'lien_zoom': reservation.lien_zoom or '', 'lien_zoom': reservation.lien_zoom or '',
} }
return JsonResponse(reservation_json, safe=True) return JsonResponse(reservation_json, safe=True)
@login_required @login_required
@@ -221,33 +216,36 @@ def valider_reservation(request: HttpRequest):
reservation.statut = 'validee' reservation.statut = 'validee'
reservation.save() reservation.save()
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle",
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.""",
destinataires = [reservation.employe.user.email]
)
fonctions_utilitaire.envoyer_mail( messages.success(request, f"Réservation de {reservation.employe.get_full_name()} validée avec succès.")
sujet = "Reservation de salle",
message = f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.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.""",
destinataires = [reservation.employe.user.email]
)
messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} 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""" """Refuser une réservation"""
data = json.loads(request.body) if request.method == 'POST':
reservation_id = data.get("id_reservation") reservation_id = request.POST.get('id_reservation')
try:
reservation = Reservation.objects.get(id=reservation_id) try:
except Reservation.DoesNotExist: reservation = Reservation.objects.get(id=reservation_id)
return JsonResponse({"message": "La resevertion selectionné n'existe pas."}) except Reservation.DoesNotExist:
else: messages.error(request, "La réservation n'existe pas.")
reservation.statut = "refusee" return redirect("salle")
reservation.statut = 'refusee'
reservation.save() reservation.save()
fonctions_utilitaire.envoyer_mail( if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
sujet = "Reservation de salle", fonctions_utilitaire.envoyer_mail(
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.""", sujet = "Reservation de salle",
destinataires = [reservation.employe.user.email] 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.""",
) destinataires = [reservation.employe.user.email]
)
return JsonResponse({"message": "Réservation refusée avec succès."}) return JsonResponse({"message": "Réservation refusée avec succès."})

301
notification/services.py Normal file
View File

@@ -0,0 +1,301 @@
from django.core.mail import send_mail
from django.conf import settings
def send_notification_email(user, sujet, message):
"""
Fonction générique pour envoyer un email simple
"""
if not user.email:
return
send_mail(
sujet,
message,
settings.DEFAULT_FROM_EMAIL,
[user.email],
fail_silently=False,
)
def email_reservation_creee(reservation):
user = reservation.employe.user
message = f"""
Bonjour {user.get_full_name()},
Votre réservation a été enregistrée avec succès.
Salle : {reservation.salle}
Date : {reservation.date_debut}
Heure : {reservation.heure_debut} - {reservation.heure_fin}
Motif : {reservation.motif_reservation}
Merci.
"""
send_notification_email(user, "Confirmation de votre réservation", message)
def email_reservation_directeur(reservation, directeur):
message = f"""
Bonjour {directeur.get_full_name()},
Une nouvelle réservation attend votre validation.
Employé : {reservation.employe.user.get_full_name()}
Salle : {reservation.salle}
Date : Du {reservation.date_debut} au {reservation.date_fin}
Heure : {reservation.heure_debut} - {reservation.heure_fin}
Motif : {reservation.motif_reservation}
Connectez-vous à la plateforme pour valider.
https://support.cerfig.org/login/
Cordialement,
SIRH
"""
send_notification_email(directeur, "Nouvelle réservation à valider", message)
def email_reservation_zoom(reservation, admin):
message = f"""
Bonjour {admin.get_full_name()},
Une demande de lien Zoom a été faite.
Employé : {reservation.employe.user.get_full_name()}
Date : {reservation.date_debut} au {reservation.date_fin}
Heure : {reservation.heure_debut} - {reservation.heure_fin}
Motif : {reservation.motif_reservation}
Veuillez créer le lien Zoom.
https://support.cerfig.org/login/
Cordialement,
SIRH
"""
send_notification_email(admin, "Création lien Zoom requise", message)
def email_statut_reservation(reservation):
user = reservation.employe.user
message = f"""
Bonjour {user.get_full_name()},
Le statut de votre réservation a été mis à jour.
Salle : {reservation.salle}
Date : {reservation.date_debut}
Statut : {reservation.statut}
Merci de vous connecter
https://support.cerfig.org
Cordialement,
SIRH
"""
send_notification_email(user, "Mise à jour de votre réservation", message)
def email_reservation_validee(reservation):
user = reservation.employe.user
message = f"""
Bonjour {user.get_full_name()},
Votre réservation a été VALIDÉE.
Salle : {reservation.salle}
Date : {reservation.date_debut}
https://support.cerfig.org/login/
Cordialement,
SIRH
"""
send_notification_email(user, "Réservation validée", message)
def email_reservation_refusee(reservation):
user = reservation.employe.user
message = f"""
Bonjour {user.get_full_name()},
Votre réservation a été REFUSÉE.
Salle : {reservation.salle}
Date : {reservation.date_debut}
https://support.cerfig.org/login/
Cordialement,
SIRH
"""
send_notification_email(user, "Réservation refusée", message)
def email_contrat_cree(contrat):
user = contrat.employe.user
message = f"""
Bonjour {user.get_full_name()},
Un nouveau contrat a été créé pour vous.
Date début : {contrat.date_debut}
Date fin : {contrat.date_fin}
Bienvenue !
Merci de vous connecter pour plus informations sur votre contrat
https://support.cerfig.org
Cordialement,
SIRH
"""
send_notification_email(user, "Nouveau contrat", message)
def email_statut_contrat(contrat):
user = contrat.employe.user
message = f"""
Bonjour {user.get_full_name()},
Votre contrat a été modifier par les ressource humaine .
veuillez vous connecter pour voir les nouvelles informations sur votre contrat
https://support.cerfig.org/
Cordialement,
SIRH
"""
send_notification_email(user, "Mise à jour du contrat", message)
def email_affectation_projet(affectation):
user = affectation.employe.user
projet = affectation.projet
message = f"""
Bonjour {user.get_full_name()},
Votre affectation au projet a été mise à jour.
Projet : {projet.nom}
Description : {projet.description}
Date début : {projet.date_debut}
Date fin : {projet.date_fin}
Rôle : {affectation.role}
Cordialement,
SIRH
"""
send_notification_email(user, "Affectation projet", message)
def email_expiration_fichier(projet, fichier, chef_projet):
message = f"""
Bonjour {chef_projet.get_full_name()},
Un fichier est proche de son expiration.
Projet : {projet.nom}
Fichier : {fichier.nom}
Date début : {fichier.date_debut}
Date fin : {fichier.date_fin}
https://support.cerfig.org/
Cordialement,
SIRH
"""
send_notification_email(chef_projet, "Fichier proche expiration", message)
def email_conge_cree(conge):
user = conge.employe.user
message = f"""
Bonjour {user.get_full_name()},
Votre demande de congé a été enregistrée avec succès.
Type de congé : {conge.type_conge}
Date début : {conge.date_debut}
Date fin : {conge.date_fin}
Motif : {conge.motif}
Votre demande est en attente de validation.
https://support.cerfig.org/
Cordialement,
SIRH
"""
send_notification_email(user, "Demande de congé enregistrée", message)
def email_conge_responsable(conge, responsable):
message = f"""
Bonjour {responsable.get_full_name()},
Une demande de congé nécessite votre validation.
Employé : {conge.employe.user.get_full_name()}
Type : {conge.type_conge}
Date : {conge.date_debut} au {conge.date_fin}
Motif : {conge.motif}
Merci de valider ou refuser.
https://support.cerfig.org/
Cordialement,
SIRH
"""
send_notification_email(responsable, "Validation congé (Niveau 1)", message)
def email_conge_directeur(conge, directeur):
message = f"""
Bonjour {directeur.get_full_name()},
Une demande de congé a été validée par le responsable et attend votre validation finale.
Employé : {conge.employe.user.get_full_name()}
Type : {conge.type_conge}
Date : {conge.date_debut} au {conge.date_fin}
Merci de valider définitivement.
https://support.cerfig.org/login/
Cordialement,
SIRH
"""
send_notification_email(directeur, "Validation congé (Niveau 2)", message)
def email_conge_valide(conge):
user = conge.employe.user
message = f"""
Bonjour {user.get_full_name()},
Votre demande de congé a été VALIDÉE.
Type : {conge.type_conge}
Date : {conge.date_debut} au {conge.date_fin}
Bon repos a vous
https://support.cerfig.org
Cordialement,
SIRH
"""
send_notification_email(user, "Congé validé", message)
def email_conge_refuse(conge):
user = conge.employe.user
message = f"""
Bonjour {user.get_full_name()},
Votre demande de congé a été REFUSÉE.
Type : {conge.type_conge}
Date : {conge.date_debut} au {conge.date_fin}
Motif : {conge.motif_refus or "Non précisé"}
https://support.cerfig.org
Cordialement,
SIRH
"""
send_notification_email(user, "Congé refusé", message)

View File

@@ -10,7 +10,6 @@ numpy==2.2.6
pandas==2.3.3 pandas==2.3.3
pillow==12.2.0 pillow==12.2.0
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
python-decouple==3.8
pytz==2026.1.post1 pytz==2026.1.post1
requests==2.33.1 requests==2.33.1
six==1.17.0 six==1.17.0
@@ -18,5 +17,5 @@ sqlparse==0.5.5
typing_extensions==4.15.0 typing_extensions==4.15.0
tzdata==2026.2 tzdata==2026.2
urllib3==2.6.3 urllib3==2.6.3
webservices==0.7 python-decouple
webservices.naudit==0.7.2 gunicorn

View File

@@ -3,23 +3,7 @@ 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
<<<<<<< HEAD
// layout : "fitColumns",
=======
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)
=======
// layout : "fitColumns",
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
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:{
@@ -31,23 +15,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
<<<<<<< HEAD
{"title": "Date de la demande", "field": "date_demande", responsive: 1},
=======
{"title": "Date de la demande", "field": "date_demande"}, {"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": "Date de la demande", "field": "date_demande", responsive: 1},
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
{"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 ,
}}, }},
@@ -56,23 +24,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}}, }},
], ],
pagination: true, pagination: true,
<<<<<<< HEAD
<<<<<<< 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)
=======
paginationSize: 5,
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
}) })
const bouton_demande_conges = $("bouton-demande-conge"); const bouton_demande_conges = $("bouton-demande-conge");

View File

@@ -12,20 +12,6 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe, // ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
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)
=======
responsiveLayout : "hide"
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
}) })
fetch(url_liste_employe) fetch(url_liste_employe)

View File

@@ -16,84 +16,27 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
], ],
ajaxURL: url_certificat, ajaxURL: url_certificat,
}) })
<<<<<<< HEAD
<<<<<<< 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();
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);
fetch(url, { fetch(url, {
method: "POST", method: 'POST',
headers: { headers: {
"X-CSRFToken": csrftoken 'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
}, },
body: formData 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(response => response.json())
.then(data => alert(data.message)) .then(data => alert(data.message))
.catch(error => console.error("Erreur:", error)); })
});
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
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);
fetch(url, {
method: "POST",
headers: {
"X-CSRFToken": csrftoken
},
body: formData
})
.then(response => response.json())
.then(data => alert(data.message))
<<<<<<< HEAD
<<<<<<< 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("Erreur:", error));
});
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5

View File

@@ -1,18 +1,4 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
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)
=======
let table;
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
btnEnregistrerBailleur.addEventListener('click', function() { btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur'); const form = document.getElementById('formBailleur');
@@ -34,49 +20,4 @@ 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
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
});
document.addEventListener("DOMContentLoaded", function () {
table = new Tabulator("#table-bailleurs", {
ajaxURL: "/gestion-projet/bailleurs/",
layout: "fitColumns",
pagination: "local",
paginationSize: 5,
columns: [
{title: "#", formatter: "rownum", width: 60},
{title: "Organisme", field: "nom_organisme"},
{title: "Contact", field: "contact"},
{title: "Email", field: "email"},
{title: "Pays", field: "pays"},
],
rowDblClick: function(e, row) {
const data = row.getData();
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
supprimerBailleur(data.id);
}
}
});
});
<<<<<<< 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)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5

View File

@@ -15,21 +15,7 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
currentReservationId = data.id_reservation; currentReservationId = data.id_reservation;
<<<<<<< HEAD
<<<<<<< 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)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
$("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;
@@ -44,37 +30,11 @@ 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
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
if (data.besoin_zoom === false){
$("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"){ if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none"; $("motif_refus_container").className = "d-none";
}else{ }else{
$("motif_refus").value=data.motif_refus; $("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)
=======
if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none";
}
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
}) })
} }
}); });
@@ -120,26 +80,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;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault();
<<<<<<< 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)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
fetch( fetch(
url_annuler, url_annuler,
{ {
@@ -190,24 +130,6 @@ if($("refuserReservation")){
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
<<<<<<< HEAD
console.log(idRes);
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)
=======
console.log(idRes);
e.preventDefault();
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
fetch(url, { fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@@ -239,52 +161,16 @@ 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
<<<<<<< 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)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
if(data.besoin_zoom === false){ if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< 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"){ if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none'; $("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)
=======
const id_user = $("current-user-id").dataset.userid;
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
$("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;
@@ -292,55 +178,16 @@ 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
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
=======
$("date_evenement").value=data.date_debut; $("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)
=======
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
$("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
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none";
}
<<<<<<< HEAD
=======
$("motif_refus").value=data.motif_refus; $("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)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide(); bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
modal.show(); modal.show();
}) })