4 Commits

Author SHA1 Message Date
65497cd669 Ajout de la suppression des demandes de congé
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-10-06 16:12:42 +00:00
f061f482d9 Feat: Hierarchisation des validations des demandes de reservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-15 12:46:19 +00:00
622b412a21 Fix: Permettre à un utilisateur d'annuler sa reservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-14 11:17:01 +00:00
54dc65f0d8 Feat: Envoie de mail à l'équipe informatique après reservation de salle necessitant un lien zoom ou ordinateur
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-14 10:42:12 +00:00
16 changed files with 406 additions and 95 deletions

View File

@@ -15,7 +15,20 @@ try:
except: except:
pass pass
EMAIL_EQUIPE_INFORMATIQUE = ["support@cerfig.org"] try:
EMAIL_EQUIPE_INFORMATIQUE = list(
Employe.objects.filter(departement__nom="Systeme informatique").values_list('user__email', flat=True)
) or ["support@cerfig.org"]
except:
EMAIL_EQUIPE_INFORMATIQUE = ["support@cerfig.org"]
def est_equipe_informatique(employe):
"""Vérifie si un employé appartient au département informatique"""
return employe.departement and employe.departement.nom == "Systeme informatique"
def est_assistante_direction(employe):
"""Vérifie si un employé est l'assistante de direction"""
return employe.fonction == "assistant_direction"
def solde_conge(employe): def solde_conge(employe):
"""Fonction de calcul du solde de congé restant l'employé""" """Fonction de calcul du solde de congé restant l'employé"""
@@ -60,4 +73,4 @@ def envoyer_mail(sujet, message, destinataires):
settings.EMAIL_HOST_USER, settings.EMAIL_HOST_USER,
destinataires, destinataires,
fail_silently=False, fail_silently=False,
) )

View File

@@ -2,6 +2,34 @@ 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
function supprimerDemandeConge(congeId) {
if (!window.confirm("Voulez-vous vraiment supprimer cette demande de congé en attente ?")) {
return;
}
const form = $("form-detail-conge");
const csrftoken = new FormData(form).get("csrfmiddlewaretoken");
const url = $("bouton-supprimer-demande-conge").dataset.url.replace(
"/0/",
`/${congeId}/`
);
fetch(url, {
method: "POST",
headers: {"X-CSRFToken": csrftoken},
})
.then(response => response.json().then(data => ({response, data})))
.then(({response, data}) => {
window.alert(data.message);
if (response.ok && data.success) {
window.location.reload();
}
})
.catch(() => {
window.alert("La suppression a échoué. Veuillez réessayer.");
});
}
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
columns: [ columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"}, {"title": "Nom et Prénom", "field": "prenom_nom"},
@@ -21,12 +49,31 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
{"title": "Validation par supérieur hiérarchique", "field": "validation_direction", formatter:"tickCross", formatterParams :{ {"title": "Validation par supérieur hiérarchique", "field": "validation_direction", formatter:"tickCross", formatterParams :{
allowEmpty : true , allowEmpty : true ,
}}, }},
{
title: "Action",
hozAlign: "center",
formatter: (cell) => {
const data = cell.getRow().getData();
if (!data.est_proprietaire || !data.en_attente) {
return "";
}
return '<button type="button" class="btn btn-danger btn-sm">Supprimer</button>';
},
cellClick: (event, cell) => {
const data = cell.getRow().getData();
if (data.est_proprietaire && data.en_attente) {
event.stopPropagation();
supprimerDemandeConge(data.id);
}
},
},
], ],
pagination: true, pagination: true,
paginationSize: 5, paginationSize: 5,
}) })
const bouton_demande_conges = $("bouton-demande-conge"); const bouton_demande_conges = $("bouton-demande-conge");
const bouton_supprimer_conge = $("bouton-supprimer-demande-conge");
bouton_demande_conges.addEventListener("click", (e) => { bouton_demande_conges.addEventListener("click", (e) => {
var modalDemandeConge = new bootstrap.Modal(document.getElementById('modalDemandeConge')); var modalDemandeConge = new bootstrap.Modal(document.getElementById('modalDemandeConge'));
@@ -44,6 +91,11 @@ tableau_liste_demande_conge.on("rowClick", function(row, rowData) {
$("nombre_jours").value = data.nombre_jours; $("nombre_jours").value = data.nombre_jours;
$("solde_restant").value = data.solde_conge; $("solde_restant").value = data.solde_conge;
$("motif_refus").value = data.motif_refus; $("motif_refus").value = data.motif_refus;
bouton_supprimer_conge.classList.toggle(
"d-none",
!(data.est_proprietaire && data.en_attente)
);
bouton_supprimer_conge.dataset.congeId = data.id;
if($("validation_hierarchique_valide") & $("validation_hierarchique_refuse")){ if($("validation_hierarchique_valide") & $("validation_hierarchique_refuse")){
$("validation_hierarchique_valide").checked = data.validation_hierarchique === true; $("validation_hierarchique_valide").checked = data.validation_hierarchique === true;
@@ -59,6 +111,10 @@ tableau_liste_demande_conge.on("rowClick", function(row, rowData) {
modal.show(); modal.show();
}); });
bouton_supprimer_conge.addEventListener("click", () => {
supprimerDemandeConge(bouton_supprimer_conge.dataset.congeId);
});
fetch(url_liste_conge_attente) fetch(url_liste_conge_attente)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {

View File

@@ -83,6 +83,13 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
{% if conge.validation_hierarchique == None and conge.validation_direction == None%}
<button type="button" class="btn btn-danger " id="bouton-supprimer-demande-conge"
data-url="{% url 'gestion_conges:supprimer-demande-conge' 0 %}"
>
Supprimer ma demande
</button>
{% endif %}
{% if employe_est_il_chef or membre_de_la_direction or est_chef_projet %} {% if employe_est_il_chef or membre_de_la_direction or est_chef_projet %}
<button type="button" class="btn btn-success" id="bouton-enregistrer-detail-conge">Enregistrer</button> <button type="button" class="btn btn-success" id="bouton-enregistrer-detail-conge">Enregistrer</button>
{% endif %} {% endif %}

View File

@@ -24,4 +24,9 @@ urlpatterns = [
views.validation_de_conge, views.validation_de_conge,
name='validation-des-conges' name='validation-des-conges'
), ),
path(
'demande/<int:conge_id>/supprimer/',
views.supprimer_demande_conge,
name='supprimer-demande-conge',
),
] ]

View File

@@ -234,4 +234,56 @@ def validation_de_conge(request):
conge.motif_refus = motif_refus if validation_direction == "refuse" else "" conge.motif_refus = motif_refus if validation_direction == "refuse" else ""
conge.save() conge.save()
return JsonResponse({"message": "La décision a été enregistrée avec succès."}) return JsonResponse({"message": "La décision a été enregistrée avec succès."})
def _conge_est_en_attente(conge):
return (
conge.validation_hierarchique is not False
and conge.validation_direction is not False
and not (
conge.validation_hierarchique is True
and conge.validation_direction is True
)
)
@login_required
def supprimer_demande_conge(request, conge_id):
if request.method != "POST":
return JsonResponse(
{"success": False, "message": "Méthode non autorisée."},
status=405,
)
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
return JsonResponse(
{"success": False, "message": "Profil employé introuvable."},
status=403,
)
try:
conge = Conge.objects.get(pk=conge_id, employe=employe)
except Conge.DoesNotExist:
return JsonResponse(
{"success": False, "message": "Demande introuvable ou accès non autorisé."},
status=404,
)
if not _conge_est_en_attente(conge):
return JsonResponse(
{
"success": False,
"message": "Seules les demandes en attente peuvent être supprimées.",
},
status=400,
)
conge.delete()
return JsonResponse(
{"success": True, "message": "Votre demande de congé en attente a été supprimée."}
)

View File

@@ -3,8 +3,8 @@ from datetime import datetime, timedelta
import requests import requests
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from decouple import config
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password from django.contrib.auth.password_validation import validate_password
@@ -27,6 +27,18 @@ def index(request):
if not request.user.groups.filter(name="ressource_humaine").exists(): if not request.user.groups.filter(name="ressource_humaine").exists():
return redirect('gestion_employe:mon-profil') return redirect('gestion_employe:mon-profil')
response = requests.get(f"{config('SERVEUR_AUTHENTIFICATION')}recuperer-list-departement/")
if response.status_code == 200:
try:
departements_data = response.json()
for dept in departements_data:
Departement.objects.update_or_create(
nom = dept['nom']
)
except json.JSONDecodeError as e:
print(f"[ERROR] Erreur lors du décodage JSON : {e}")
employes = Employe.objects.all().order_by('-user__date_joined') employes = Employe.objects.all().order_by('-user__date_joined')
nombre_employes = Employe.objects.count() nombre_employes = Employe.objects.count()
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count() nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
@@ -521,7 +533,7 @@ def modifier_mot_passe(request):
} }
response = requests.post( response = requests.post(
"http://localhost:8000/modifier-mot-de-passe/", f"{config('SERVEUR_AUTHENTIFICATION')}modifier-mot-de-passe/",
json=donnees, json=donnees,
headers=headers headers=headers
) )

View File

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

View File

@@ -92,9 +92,6 @@ $("bouton-annuler").addEventListener("click", (e) => {
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler; const url_annuler = $("formulaire-details").dataset.urlannuler;
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault(); e.preventDefault();
fetch( fetch(
@@ -189,7 +186,7 @@ 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; 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;
@@ -206,8 +203,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 refuserReservation = document.querySelector("#refuserReservation");
const bouton_valider = document.querySelector("#bouton-valider"); const bouton_valider = document.querySelector("#bouton-valider");

View File

@@ -50,19 +50,23 @@
<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 /> <hr />
<div class='d-flex justify-content-around mt-2'> <div class='d-flex justify-content-center mt-2'>
{% if appartient_au_departement_informatique %} {% if est_equipe_informatique %}
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button> <button class="btn btn-primary me-1" id="ajoutZoom">Ajout du lien zoom</button>
{% endif %} {% endif %}
{% if appartient_direction %} {% if est_assistante_direction %}
<button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button> <button class="btn btn-danger me-1" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
<button class="btn btn-success" id="bouton-valider">Valider</button> <button class="btn btn-success me-1" id="bouton-valider">Valider</button>
{% endif %} {% endif %}
<span id="current-user-id" data-user-id="{{ request.user.id }}"></span> {% if est_equipe_informatique %}
<button class="btn btn-danger me-1" id="refuserReservationInfo" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
<button class="btn btn-success me-1" id="bouton-valider-info">Valider</button>
{% endif %}
<span id="current-user-id" data-user-id="{{ user.id }}"></span>
<button class="btn btn-danger" id="bouton-annuler">Annuler</button> <button class="btn btn-danger" id="bouton-annuler">Annuler</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

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