3 Commits

Author SHA1 Message Date
c28b14fb98 clean: remove pycache from tracking 2026-04-30 13:28:57 +02:00
29a93e9bfe clean: remove pycache from tracking 2026-04-30 13:28:53 +02:00
a97c233ddb feature: ajout photo de profil 2026-04-30 13:21:16 +02:00
54 changed files with 391 additions and 460 deletions

13
.gitignore vendored
View File

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

52
Jenkinsfile vendored
View File

@@ -1,52 +0,0 @@
pipeline
{
agent any
options {
// This is required if you want to clean before build
skipDefaultCheckout(true)
}
environment
{
SUDO_PASSWORD = credentials('sudo-password')
}
stages
{
stage ( 'checkout' )
{
steps
{
sh 'echo "Debut du pipeline"'
cleanWs()
checkout scm
}
}
stage ( 'Deploiement' )
{
when { branch 'main' }
steps {
sh '''
cd /var/www/sirh
echo $SUDO_PASSWORD | sudo -S chown -R jenkins:jenkins /var/www/sirh
git fetch origin main
git reset --hard origin/main
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
python manage.py makemigrations
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 "Deploiement reussi"
echo $SUDO_PASSWORD | sudo -S supervisorctl restart sirh
'''
}
}
}
}

View File

@@ -12,21 +12,21 @@ 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
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY') SECRET_KEY = 'django-insecure--wdb9t(77rvyac$_q!n5gw86&0r(0&&j171v9h!-_$jahsza*5'
# 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 = False
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',') ALLOWED_HOSTS = ["https://support.cerfig.org", "support.cerfig.org"]
# Application definition # Application definition
@@ -79,25 +79,24 @@ 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 = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': 'sirh',
# 'USER': 'sirh',
# 'PASSWORD': 'sirh-cerfig',
# 'HOST': 'localhost',
# 'PORT': '3306',
# }
# }
if config('ENVIRONMENT') == 'local': DATABASES = {
DATABASES = { 'default': {
'default': { 'ENGINE': 'django.db.backends.sqlite3',
'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DATABASE_NAME'),
'USER': config('DATABASE_USER'),
'PASSWORD': config('DATABASE_PASSWORD'),
'HOST': config('DATABASE_HOST'),
'PORT': config('DATABASE_PORT'),
}
} }
}
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
@@ -150,7 +149,6 @@ MEDIA_ROOT = BASE_DIR / "media"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# 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

View File

@@ -6,13 +6,13 @@
{% if user.employe.photo %} {% if user.employe.photo %}
<img src="{{ user.employe.photo.url }}" <img src="{{ user.employe.photo.url }}"
class="rounded-circle" class="rounded-circle"
width="100" width="80"
height="100" height="80"
style="object-fit:cover;"> style="object-fit:cover;">
{% else %} {% else %}
<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">
{{ user.username }} {{ user.username }}
</div> </div>
</div> </div>
@@ -41,6 +41,15 @@
<a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <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 %} <a href="{% url 'rapport-rh' %}" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
<i class="bi bi-graph-up"></i> Rapports et Statistiques
</a> {% endcomment %}
{% comment %} <a href="" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
<i class="bi bi-person-gear"></i> Gestion des Utilisateurs
</a> {% endcomment %}
{% comment %} <a href="{% url 'gestion_employe:departement' %}" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
<i class="bi bi-gear"></i> Paramètres
</a> {% 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.4em">
<i class="bi bi-box-arrow-right"></i> Déconnexion <i class="bi bi-box-arrow-right"></i> Déconnexion
</a> </a>

View File

@@ -1,5 +1,5 @@
from django.utils import timezone from django.utils import timezone
from gestion_employe.models import Contrat, Employe from gestion_employe.models import Contrat
from gestion_conge.models import Conge from gestion_conge.models import Conge
@@ -7,7 +7,6 @@ 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))
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é"""
@@ -32,26 +31,4 @@ def solde_conge(employe):
"success": True, "success": True,
"quota_annuel": QUOTA_CONGE_ANNUEL - jours_conges_valider, "quota_annuel": QUOTA_CONGE_ANNUEL - jours_conges_valider,
"nombre_jours_valide": jours_conges_valider "nombre_jours_valide": jours_conges_valider
} }
def envoyer_mail(sujet, message, destinataires):
"""Fonction d'envoi de mail"""
from django.core.mail import send_mail
from django.conf import settings
send_mail(
sujet,
message,
settings.EMAIL_HOST_USER,
destinataires,
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

@@ -83,6 +83,7 @@ def index(request):
Q(validation_hierarchique = True) | Q(validation_hierarchique = False) Q(validation_hierarchique = True) | Q(validation_hierarchique = False)
).order_by('-date_demande') ).order_by('-date_demande')
return render(request, 'gestion_conge/index.html', { return render(request, 'gestion_conge/index.html', {
"nombre_conges_valide": nombre_conges_valide, "nombre_conges_valide": nombre_conges_valide,
"nombre_conges_refuse": nombre_conges_refuse, "nombre_conges_refuse": nombre_conges_refuse,
@@ -122,82 +123,72 @@ def demander_conge(request):
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.")
fonctions_utilitaire.envoyer_mail(
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.""",
destinataires = fonctions_utilitaire.destinataire_mail_demande_conges() + [employe.user.email]
)
return redirect("gestion_conges:conge") return redirect("gestion_conges:conge")
return redirect("gestion_conges:conge") return redirect("gestion_conges:conge")
@login_required @login_required
def liste_demande_conges(request): def liste_demande_conges(request):
"""Vue de liste des demandes de congés en attente de validation selon le statut de l'utilisateur actuel"""
try: try:
employe = Employe.objects.get(user=request.user) employe = Employe.objects.get(user__username = request.user)
except Employe.DoesNotExist: except Employe.DoesNotExist:
return JsonResponse({ return JsonResponse({
"success": False, "success": False,
"message": "Profil employé introuvable" "message": "Votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'administrateur."
}) })
affectation = Affectation.objects.filter( try:
employe=employe, affectation = Affectation.objects.get(
date_fin_daffectation__gte=timezone.now().date() employe=employe,
).first() date_fin_daffectation__gte=timezone.now().date()
)
is_direction = employe.user.groups.filter(name='direction').exists() except Affectation.DoesNotExist:
affectation = None
if employe.chef: if employe.chef:
print("chef")
conges = Conge.objects.filter( conges_en_attente = Conge.objects.filter(
Q(employe__departement=employe.departement) | employe__departement = employe.departement,
Q(employe=employe) validation_hierarchique = None
).order_by('-date_demande') ).order_by('-date_demande')
elif affectation and affectation.role == "chef_projet": elif affectation and affectation.role == "chef_projet":
employes_du_projet = Affectation.objects.filter( employes_du_projet = Affectation.objects.filter(
projet=affectation.projet, projet = affectation.projet,
date_fin_daffectation__gte=timezone.now().date() date_fin_daffectation__gte = timezone.now().date()
).values_list('employe', flat=True) ).values('employe')
conges = Conge.objects.filter( conges_en_attente = Conge.objects.filter(
Q(employe__in=employes_du_projet) | employe__in = employes_du_projet,
Q(employe=employe) validation_hierarchique = None
).order_by('-date_demande') ).order_by('-date_demande')
elif 'direction' in employe.user.groups.values_list('name', flat=True):
elif is_direction: conges_en_attente = Conge.objects.filter(
validation_hierarchique = True,
conges = Conge.objects.filter( validation_direction = None
Q(validation_hierarchique=True) | ).order_by('-date_demande')
Q(employe__user__groups__name='direction')
).distinct().order_by('-date_demande')
else: else:
conges_en_attente = Conge.objects.filter(
conges = Conge.objects.filter( employe__user__username = request.user
employe=employe
).order_by('-date_demande') ).order_by('-date_demande')
return JsonResponse({ return JsonResponse({
"success": True, "success": True,
"data": [ "data":[
{ {
**model_to_dict(conge), **model_to_dict(conge),
"prenom_nom": f"{conge.employe.user.first_name} {conge.employe.user.last_name}", "prenom_nom": f"{conge.employe.user.first_name} {conge.employe.user.last_name}",
"date_demande": conge.date_demande, "date_demande": conge.date_demande,
"nombre_jours": conge.nombre_jours, "nombre_jours": conge.nombre_jours,
"type": dict(conge.TYPE_CHOICES).get(conge.type), "type": dict(conge.TYPE_CHOICES).get(conge.type),
"solde_conge": fonctions_utilitaire.solde_conge(conge.employe)["quota_annuel"] "solde_conge": fonctions_utilitaire.solde_conge(conge.employe)["quota_annuel"]
} }
for conge in conges for conge in conges_en_attente]},
] safe=False
}) )
@login_required @login_required
def validation_de_conge(request): def validation_de_conge(request):

View File

@@ -15,7 +15,6 @@ class Employe(models.Model):
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'),
('comptable', 'Comptable'), ('comptable', 'Comptable'),
('raf', 'RAF'), ('raf', 'RAF'),
('data_manager', 'Data Manager'), ('data_manager', 'Data Manager'),

View File

@@ -42,7 +42,7 @@
{% csrf_token %} {% csrf_token %}
<div class="col"> <div class="col">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Photo</label> <label>photo</label>
{% if employe.photo %} {% if employe.photo %}
<span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span> <span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span>
{% endif %} {% endif %}

View File

@@ -79,6 +79,21 @@ urlpatterns = [
views.modifier_mot_passe, views.modifier_mot_passe,
name='modifier-mot-passe' name='modifier-mot-passe'
), ),
# path(
# 'creation-departement/',
# views.creation_departement,
# name='creation-departement'
# ),
# path(
# 'modifier-departement/',
# views.modifier_departement,
# name='modifier-departement'
# ),
# path(
# 'suppression-departement/',
# views.supprimer_departement,
# name='suppression-departement/'
# ),
path( path(
"liste-contrat-expirants", "liste-contrat-expirants",
views.liste_contrat_expirants, views.liste_contrat_expirants,

View File

@@ -1,5 +1,5 @@
import json import json
from datetime import date, timedelta, datetime from datetime import timedelta, datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.utils import timezone from django.utils import timezone
@@ -109,13 +109,6 @@ def affecter_employe_projet(request):
} }
) )
messages.success(request, f"L'employé {employe.user.first_name} {employe.user.last_name} a été affecté au projet {projet.nom_projet}.") messages.success(request, f"L'employé {employe.user.first_name} {employe.user.last_name} a été affecté au projet {projet.nom_projet}.")
fonctions_utilitaire.envoyer_mail(
sujet = "Affectation à un projet",
message = f"""Bonjour {employe.user.first_name} {employe.user.last_name}, vous avez été affecté au projet {projet.nom_projet.upper()} pour la période du {form.cleaned_data['date_affectation'].strftime('%d/%m/%Y')} au {date_fin_affectation.strftime('%d/%m/%Y')} en tant que {dict(Affectation.ROLE_CHOICES).get(form.cleaned_data['role'])}.
Veuillez consulter votre profil pour plus de détails.""",
destinataires = [employe.user.email]
)
return redirect('gestion_employe:index') return redirect('gestion_employe:index')
else: else:
messages.error(request, "Erreur : Formulaire non valide.") messages.error(request, "Erreur : Formulaire non valide.")
@@ -183,7 +176,6 @@ def modifier_mot_passe(request):
messages.success(request, "Mot de passe modifié avec succès.") messages.success(request, "Mot de passe modifié avec succès.")
return redirect("gestion_employe:mon-profil") 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é"""
try: try:
@@ -212,19 +204,13 @@ def modifier_employer(request):
return JsonResponse({"message": "Profil mis à jour avec succès."}) return JsonResponse({"message": "Profil mis à jour avec succès."})
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"""
employe = Employe.objects.get(user=request.user) employe = Employe.objects.get(user=request.user)
if request.method == "POST": if request.method == "POST":
if request.FILES.get("photo"): if request.FILES.get("photo"):employe.photo = request.FILES["photo"]
employe.photo = request.FILES["photo"] if "cv" in request.FILES:employe.CV = request.FILES["cv"]
if "cv" in request.FILES: if "diplome" in request.FILES: employe.diplome = request.FILES["diplome"]
employe.CV = request.FILES["cv"] if "rib" in request.FILES: employe.rib = request.FILES["rib"]
if "diplome" in request.FILES: if "casier_judiciaire" in request.FILES:employe.casier_judiciaire = request.FILES["casier_judiciaire"]
employe.diplome = request.FILES["diplome"]
if "rib" in request.FILES:
employe.rib = request.FILES["rib"]
if "casier_judiciaire" in request.FILES:
employe.casier_judiciaire = request.FILES["casier_judiciaire"]
employe.save() employe.save()
messages.success(request, "Documents enregistrés avec succès.") messages.success(request, "Documents enregistrés avec succès.")
@@ -254,25 +240,14 @@ def suppression_affectation(request):
return JsonResponse({"message": "Affectation supprimée avec succès."}) return JsonResponse({"message": "Affectation supprimée avec succès."})
def creation_contrat(request): def creation_contrat(request):
"""Créer un contrat pour un employé (avec contrôle d'existence de contrat actif)""" """Vue pour permettre à un utilisateur de créer un contrat pour un employé"""
try: try:
employe = Employe.objects.get(id=request.POST.get('employe_id')) employe = Employe.objects.get(id=request.POST.get('employe_id'))
except Employe.DoesNotExist: except Employe.DoesNotExist:
messages.error(request, "Employé non trouvé.") messages.error(request, "Employé non trouvé.")
return redirect('employe-index') return redirect('employe-index')
contrat_actif = Contrat.objects.filter(
employe=employe,
date_fin__gte=date.today()
).exists()
if request.method == "POST": if request.method == "POST":
if contrat_actif:
messages.error(
request,
"Impossible de créer un contrat : cet employé a déjà un contrat actif."
)
return redirect('gestion_employe:index')
form = ContratForm(request.POST, request.FILES) form = ContratForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
contrat = form.save(commit=False) contrat = form.save(commit=False)
@@ -281,13 +256,9 @@ def creation_contrat(request):
messages.success(request, "Contrat créé avec succès.") messages.success(request, "Contrat créé avec succès.")
return redirect('gestion_employe:index') return redirect('gestion_employe:index')
messages.error(request, "Formulaire non valide") messages.error(request, "Formulaire non valide")
else: else:
form = ContratForm(initial={'employe': employe}) form = ContratForm(initial={'employe': employe})
return render(request, 'gestion_employe/index.html', {'contrat_form': form})
return render(request, 'gestion_employe/index.html', {
'contrat_form': form
})
@login_required @login_required
def enregistrer_detail_employe(request): def enregistrer_detail_employe(request):
@@ -436,3 +407,41 @@ 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 creation_departement(request):
# """Gère la création d'un nouveau département via un formulaire."""
# if request.method == 'POST':
# form_departement = DepartementForm(request.POST)
# if form_departement.is_valid():
# form_departement.save()
# messages.success(request, "Département ajouté avec succès.")
# else:
# messages.error(request, "Erreur lors de l'ajout du département.")
# return redirect('parametres-rh')
# @login_required
# def modifier_departement(request, id):
# """Gère la modification d'un département existant via un formulaire pré-rempli."""
# departement = Departement.objects.get(id=id)
# form = DepartementForm(instance=departement)
# if request.method == 'POST':
# nouveau_nom_departement = request.POST.get('nom')
# if nouveau_nom_departement:
# departement.nom = nouveau_nom_departement
# departement.save()
# messages.success(request, "Département modifié avec succès.")
# return redirect('parametres-rh')
# return render(request, 'gestion_employe/edit_departement.html', {
# 'form': form,
# 'departement': departement
# })
# @login_required
# def supprimer_departement(request, id):
# """Gère la suppression d'un département existant."""
# if request.method == "POST":
# departement = Departement.objects.get(id=id)
# departement.delete()
# messages.success(request, "Département supprimé avec succès !")
# return redirect('parametres-rh')

View File

@@ -1,5 +1,4 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
let table;
btnEnregistrerBailleur.addEventListener('click', function() { btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur'); const form = document.getElementById('formBailleur');
@@ -21,31 +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.');
} }
}); });
}); });
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);
}
}
});
});

View File

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

View File

@@ -19,12 +19,6 @@ urlpatterns = [
views.creation_projet, views.creation_projet,
name='creation-projet' name='creation-projet'
), ),
path(
'bailleurs/',
views.liste_bailleur,
name='liste-bailleurs'
),
path( path(
'projet/modifier/<int:projet_id>/', 'projet/modifier/<int:projet_id>/',
views.modification_projet, views.modification_projet,
@@ -90,7 +84,6 @@ urlpatterns = [
views.liste_activites_projet, views.liste_activites_projet,
name='liste-activites-projet' name='liste-activites-projet'
), ),
# path( # path(
# 'projet/ajout-de-document/', # 'projet/ajout-de-document/',
# views.ajouter_document_projet, # views.ajouter_document_projet,
@@ -126,6 +119,4 @@ urlpatterns = [
views.mises_a_jour_projet, views.mises_a_jour_projet,
name='mises-a-jour-projet' name='mises-a-jour-projet'
) )
]
]

View File

@@ -143,22 +143,6 @@ def creation_bailleur(request):
return JsonResponse({'success': True}) return JsonResponse({'success': True})
return JsonResponse({'success': False}) return JsonResponse({'success': False})
@login_required
def liste_bailleur(request):
""" Vue pour retourner la liste de tous les bailleurs """
bailleurs = Bailleur.objects.all().order_by('-id')
data = []
for b in bailleurs:
data.append({
"id": b.id,
"nom_organisme": b.nom_organisme,
"contact": b.contact,
"email": b.email,
"pays": b.pays,
})
return JsonResponse(data, safe=False)
@login_required @login_required
def ajouter_financement_projet(request): def ajouter_financement_projet(request):
"""Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%""" """Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%"""
@@ -334,7 +318,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

@@ -30,4 +30,4 @@ class Reservation(models.Model):
statut = models.CharField(choices=STATUT, default='en_attente', max_length=25) statut = models.CharField(choices=STATUT, default='en_attente', max_length=25)
def __str__(self): def __str__(self):
return f"{self.salle} - {self.employe.user.first_name} {self.employe.user.last_name}" return f"{self.salle} - {self.employe.user.first_name} {self.employe.user.last_name} le {self.date_reservation}"

View File

@@ -15,6 +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;
console.log(data);
$("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;
@@ -29,10 +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;
if (data.besoin_zoom === false){ if(data.statut !== "annulee"){
$("lien_zoom_container").className = "d-none"; $("motif_refus_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
} }
}) })
} }
}); });
@@ -78,11 +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;
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault();
fetch( fetch(
url_annuler, url_annuler,
{ {
@@ -133,9 +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;
console.log(idRes);
e.preventDefault();
fetch(url, { fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@@ -167,11 +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();
console.log(data);
if(data.besoin_zoom === false){ if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
const id_user = $("current-user-id").dataset.userid; if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
$("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;
@@ -179,18 +178,14 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe; $("employe").value=data.employe;
$("salle").value=data.salle; $("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut; $("statut-reservation").innerHTML=data.statut;
$("date_debut").value = data.date_debut; $("date_evenement").value=data.date_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;
$("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;
$("motif_refus").value=data.motif_refus;
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();

View File

@@ -26,9 +26,9 @@
<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>

View File

@@ -1,5 +1,4 @@
<!-- 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">
@@ -22,12 +21,8 @@
<input class="form-control" id="salle" readonly> <input class="form-control" id="salle" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Date de debut :</label> <label>Date de l'évènement :</label>
<input type='date' class="form-control" id="date_debut" readonly > <input type='date' class="form-control" id="date_evenement" readonly >
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type='date' class="form-control" id="date_fin" readonly >
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Heure de début :</label> <label>Heure de début :</label>
@@ -53,19 +48,24 @@
<label label="form-check-label">Besoin d'un ordinateur</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>
<div class="form-group mb-2" id='motif_refus_container'>
<label>Motif de refus de la reservation :</label>
<textarea class="form-control" id="motif_refus" readonly></textarea>
</div>
<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 user|has_group:'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>
{% endif %}
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
{% if appartient_direction %}
<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-userid="{{ request.user.id }}"></span>
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,111 +1,74 @@
from datetime import timedelta
import json import json
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.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.http import JsonResponse, HttpRequest from django.http import JsonResponse, HttpRequest
from django.forms import model_to_dict from django.forms import model_to_dict
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
@login_required @login_required
def index(request: HttpRequest): def index(request:HttpRequest):
"""Vue de gestion de la reservation de la salle"""
try: try:
employe = Employe.objects.get(user=request.user) employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist: except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.") messages.error(request, "Impossible d'accéder au menu 'Reservation de salle' car votre profil Utilisateur n'est lié à aucun profil Employe. Veuillez contacter l'administrateur.")
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)
if form.is_valid(): if form.is_valid():
date_debut = form.cleaned_data['date_debut'] date_debut = form.cleaned_data.get('date_debut')
date_fin = form.cleaned_data['date_fin'] date_fin = form.cleaned_data.get('date_fin')
salle = form.cleaned_data['salle'] salle = form.cleaned_data.get('salle')
heure_debut = form.cleaned_data['heure_debut'] heure_debut = form.cleaned_data.get('heure_debut')
heure_fin = form.cleaned_data['heure_fin'] heure_fin = form.cleaned_data.get('heure_fin')
motif = form.cleaned_data['motif_reservation'] motif_reservation = form.cleaned_data.get('motif_reservation')
besoin_zoom = form.cleaned_data['besoin_zoom'] besoin_zoom = form.cleaned_data.get('besoin_zoom')
besoin_ordi = form.cleaned_data['besoin_ordi'] besoin_ordi = form.cleaned_data.get('besoin_ordi')
if date_fin < date_debut:
messages.error(request, "Date fin invalide.")
return redirect('gestion_salle:reservation-salle')
if heure_fin <= heure_debut:
messages.error(request, "Heure invalide.")
return redirect('gestion_salle:reservation-salle')
if not request.user.first_name.strip() or not request.user.last_name.strip(): while date_debut <= date_fin :
messages.error( reservation = Reservation(
request, employe = employe,
"Veuillez renseigner votre nom et prénom pour pouvoir faire une réservation." date_debut = date_debut,
date_fin = date_debut,
salle = salle,
heure_debut = heure_debut,
heure_fin = heure_fin,
besoin_zoom = besoin_zoom,
besoin_ordi = besoin_ordi,
motif_reservation=motif_reservation,
) )
return redirect('gestion_salle:reservation-salle') reservation.save()
date_debut = date_debut + timedelta(days=1)
created = [] messages.success(request, "Réservation(s) créées avec succès.")
current_date = date_debut
while current_date <= date_fin:
reservation = Reservation.objects.create(
employe=employe,
date_debut=current_date,
date_fin=current_date,
salle=salle,
heure_debut=heure_debut,
heure_fin=heure_fin,
besoin_zoom=besoin_zoom,
besoin_ordi=besoin_ordi,
motif_reservation=motif,
statut="en_attente"
)
created.append(reservation)
current_date += timedelta(days=1)
messages.success(request, "Réservation(s) créée(s) avec succès.")
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle",
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}".
Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires = 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.objects.get(user__username=request.user).departement
appartient_direction = 'direction' in request.user.groups.values_list('name', flat=True)
appartient_direction = request.user.groups.filter(name='direction').exists() liste_demande_reservation = [
reservation.id for reservation in
liste_demande_reservation = Reservation.objects.filter( Reservation.objects.filter(employe=employe, statut='en_attente')
employe=employe, ]
statut='en_attente'
).values_list('id', flat=True)
context = { context = {
'formulaire_reservation': formulaire_reservation, 'formulaire_reservation': formulaire_reservation,
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(), 'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique", 'appartient_au_departement_informatique': 'Informatique' == departement.nom if departement else False,
'appartient_direction': appartient_direction, 'appartient_direction': appartient_direction,
'liste_demande_reservation': list(liste_demande_reservation), 'liste_demande_reservation': 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:
color = None color = None
if reservation.statut == "en_attente": if reservation.statut == "en_attente":
@@ -115,8 +78,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),
@@ -125,6 +86,7 @@ def liste_reservation(request:HttpRequest):
"end": reservation.heure_fin, "end": reservation.heure_fin,
"color": color, "color": color,
}) })
return JsonResponse(liste_reservation, safe=False) return JsonResponse(liste_reservation, safe=False)
@login_required @login_required
@@ -134,25 +96,20 @@ 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),
} for reservation in reservations } for reservation in reservations
] ]
return JsonResponse(liste_reservation, safe=False) return JsonResponse(liste_reservation, safe=False)
def detail_reservation(request:HttpRequest, reservation_id:int): def detail_reservation(request:HttpRequest, reservation_id:int):
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
employe = reservation.employe.user employe = reservation.employe.user
reservation_json = { reservation_json = {
'id_reservation': reservation_id, 'id_reservation': reservation_id,
'employe': f"{employe.first_name} {employe.last_name}", 'employe': f"{employe.first_name} {employe.last_name}",
'salle': reservation.salle, 'salle': reservation.salle,
'statut': dict(Reservation.STATUT).get(reservation.statut), 'statut': 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'),
'heure_fin': reservation.heure_fin.strftime('%H:%M'), 'heure_fin': reservation.heure_fin.strftime('%H:%M'),
@@ -160,6 +117,7 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
'besoin_zoom': reservation.besoin_zoom, 'besoin_zoom': reservation.besoin_zoom,
'besoin_ordinateur': reservation.besoin_ordi, 'besoin_ordinateur': reservation.besoin_ordi,
'lien_zoom': reservation.lien_zoom or '', 'lien_zoom': reservation.lien_zoom or '',
'motif_refus': reservation.motif_refus or '',
} }
return JsonResponse(reservation_json, safe=True) return JsonResponse(reservation_json, safe=True)
@@ -207,48 +165,31 @@ def annuler_reservation(request:HttpRequest):
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
@login_required @login_required
def valider_reservation(request: HttpRequest): def valider_reservation(request:HttpRequest):
"""Validation d'une réservation""" """Vue de gestion de l'annulation de la reservation"""
if request.method == 'POST': if request.method == 'POST':
reservation_id = request.POST.get('id_reservation') reservation_id= request.POST['id_reservation']
try: try:
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist: except reservation.DoesNotExist:
messages.error(request, "La réservation sélectionnée n'existe pas.") messages.error(request, "La resevertion selectionné n'existe pas.")
return redirect("salle") return redirect("salle")
reservation.statut = 'validee' reservation.statut = 'validee'
reservation.save() reservation.save()
messages.success(request, f"Réservation de {reservation.employe.get_full_name()} validée avec succès.")
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]
)
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):
"""Refuser une réservation""" """Vue de gestion de refus de la reservation"""
if request.method == 'POST': data = json.loads(request.body)
reservation_id = json.loads(request.body).get('id_reservation') reservation_id = data.get("id_reservation")
try:
try: reservation = Reservation.objects.get(id=reservation_id)
reservation = Reservation.objects.get(id=reservation_id) except Reservation.DoesNotExist and ValueError:
except Reservation.DoesNotExist: return JsonResponse({"message": "La resevertion selectionné n'existe pas."})
messages.error(request, "La réservation n'existe pas.") else:
return JsonResponse({"message": "Une erreur s'est produite lors de l'annulation de la reservation."}) reservation.statut = "refusee"
reservation.statut = 'refusee'
reservation.save() reservation.save()
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é 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."})

View File

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

View File

@@ -190,4 +190,4 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
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();
}) })