5 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
Fatimac2804
29a42ee6be feat:Ajout notification email équipe informatique
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-09-10 12:23:45 +02:00
16 changed files with 408 additions and 81 deletions

View File

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

View File

@@ -2,6 +2,34 @@ const $ = (element) => document.getElementById(element);
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", {
columns: [
{"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 :{
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,
paginationSize: 5,
})
const bouton_demande_conges = $("bouton-demande-conge");
const bouton_supprimer_conge = $("bouton-supprimer-demande-conge");
bouton_demande_conges.addEventListener("click", (e) => {
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;
$("solde_restant").value = data.solde_conge;
$("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")){
$("validation_hierarchique_valide").checked = data.validation_hierarchique === true;
@@ -59,6 +111,10 @@ tableau_liste_demande_conge.on("rowClick", function(row, rowData) {
modal.show();
});
bouton_supprimer_conge.addEventListener("click", () => {
supprimerDemandeConge(bouton_supprimer_conge.dataset.congeId);
});
fetch(url_liste_conge_attente)
.then(response => response.json())
.then(data => {

View File

@@ -83,6 +83,13 @@
</div>
<div class="modal-footer">
<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 %}
<button type="button" class="btn btn-success" id="bouton-enregistrer-detail-conge">Enregistrer</button>
{% endif %}

View File

@@ -24,4 +24,9 @@ urlpatterns = [
views.validation_de_conge,
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.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
from dateutil.relativedelta import relativedelta
from decouple import config
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
@@ -27,6 +27,18 @@ def index(request):
if not request.user.groups.filter(name="ressource_humaine").exists():
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')
nombre_employes = Employe.objects.count()
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
@@ -521,7 +533,7 @@ def modifier_mot_passe(request):
}
response = requests.post(
"http://localhost:8000/modifier-mot-de-passe/",
f"{config('SERVEUR_AUTHENTIFICATION')}modifier-mot-de-passe/",
json=donnees,
headers=headers
)

View File

@@ -1,3 +1 @@
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 url_annuler = $("formulaire-details").dataset.urlannuler;
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault();
fetch(
@@ -189,7 +186,7 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("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_refus").value = data.id;
@@ -206,8 +203,6 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom;
console.log("Verification de data :", data);
const refuserReservation = document.querySelector("#refuserReservation");
const bouton_valider = document.querySelector("#bouton-valider");

View File

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

View File

@@ -3,6 +3,7 @@ from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.forms import model_to_dict
from django.http import HttpRequest, JsonResponse
from django.shortcuts import redirect, render
@@ -16,13 +17,16 @@ from .models import Reservation
@login_required
def index(request: HttpRequest):
"""Vue d'affichage de la page d'accueil de la gestion des salles"""
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.")
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":
form = ReservationForm(request.POST)
@@ -37,11 +41,11 @@ def index(request: HttpRequest):
besoin_ordi = form.cleaned_data['besoin_ordi']
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')
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')
if not request.user.first_name.strip() or not request.user.last_name.strip():
@@ -54,8 +58,9 @@ def index(request: HttpRequest):
created = []
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(
employe=employe,
date_debut=current_date,
@@ -74,18 +79,35 @@ def index(request: HttpRequest):
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(
sujet = "Reservation de salle",
message = f"""
sujet="Demande de réservation de salle",
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}".
{"Cette demande nécessite un lien Zoom et/ou un ordinateur et sera traitée par l'équipe informatique après votre validation." if besoin_zoom or besoin_ordi else ""}
Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires = fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
destinataires=fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
)
if salle == "lien_zoom" and fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
fonctions_utilitaire.envoyer_mail(
sujet="Demande de lien Zoom",
message=f"""Bonjour,
Une nouvelle demande de lien Zoom a été effectuée par {employe.user.first_name} {employe.user.last_name} du {date_debut.strftime('%d/%m/%Y')} au {date_fin.strftime('%d/%m/%Y')} de {heure_debut.strftime('%H:%M')} à {heure_fin.strftime('%H:%M')} pour le motif {motif}.
Veuillez vous connecter à la plateforme pour traiter cette demande.""",
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
)
return redirect('gestion_salle:reservation-salle')
formulaire_reservation = ReservationForm()
departement = employe.departement
# departement = employe.departement
liste_demande_reservation = Reservation.objects.filter(
employe=employe,
@@ -95,20 +117,20 @@ Veuillez vous connecter à la plateforme pour plus de détails.""",
context = {
'formulaire_reservation': formulaire_reservation,
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique",
'appartient_direction': request.user.groups.filter(name='direction').exists(),
'appartient_au_departement_informatique': est_equipe_info,
'est_assistante_direction': est_assistante,
'est_equipe_informatique': est_equipe_info,
'liste_demande_reservation': list(liste_demande_reservation),
}
return render(request, "gestion_salle/index.html", context)
def liste_reservation(request:HttpRequest):
"""Vue d'affichage des creneaux disponibles"""
reservations = Reservation.objects.filter(statut = "validee")
liste_reservation = []
for reservation in reservations:
print(reservation.statut)
color = None
if reservation.statut == "en_attente":
color = "#ffc107"
@@ -117,8 +139,6 @@ def liste_reservation(request:HttpRequest):
else:
color = "#dc3545"
print(dict(Reservation.TYPE_CHOICES).get(reservation.salle))
liste_reservation.append({
"guid": reservation.pk,
"title": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
@@ -131,7 +151,35 @@ def liste_reservation(request:HttpRequest):
@login_required
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")
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 = [
{
**model_to_dict(reservation),
@@ -167,49 +215,99 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
@login_required
def ajouter_lien_zoom(request:HttpRequest):
"""Vue de gestion de l'ajout du lien zoom"""
if request.method == 'POST':
reservation_id= request.POST['id_reservation']
try:
reservation = Reservation.objects.get(id=reservation_id)
except reservation.DoesNotExist:
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')
"""Traitement de la réservation par l'équipe informatique :
- Ajoute lien zoom si demandé.
- Valide une réservation qui exigeait un traitement par l'équipe informatique.
- Envoie un mail à toutes les équipes et au demandeur après validation.
"""
reservation = None
if request.method == "POST":
lien = request.POST.get("lien_zoom")
reservation.lien_zoom = lien
reservation.statut = "validee"
if request.method == 'POST':
reservation_id = request.POST.get('id_reservation')
try:
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()
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')
@login_required
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':
reservation_id= request.POST['id_reservation']
try:
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist:
messages.error(request, "La resevertion selectionné n'existe pas.")
return redirect("salle")
messages.error(request, "La reservation selectionné 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")
# 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.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')
@login_required
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':
reservation_id = request.POST.get('id_reservation')
@@ -217,37 +315,122 @@ def valider_reservation(request: HttpRequest):
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist:
messages.error(request, "La réservation sélectionnée n'existe pas.")
return redirect("salle")
return redirect('gestion_salle:reservation-salle')
reservation.statut = 'validee'
reservation.save()
try:
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(
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]
)
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
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')
@login_required
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)
reservation_id = data.get("id_reservation")
try:
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist:
return JsonResponse({"message": "La resevertion selectionné n'existe pas."})
else:
reservation.statut = "refusee"
reservation.save()
return JsonResponse({"message": "La reservation selectionné n'existe pas."})
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]
)
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
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."})