18 Commits

Author SHA1 Message Date
67c14cfc7e Correction sur l'affichage des detail de la reservation
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-06-18 11:31:54 +00:00
a28b177eba Correction modification profil employé
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-06-18 10:39:07 +00:00
415860f233 Filtrage des reservations par salle
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-15 17:13:02 +00:00
8503689aa4 Bug: Correction du refus des reservations et d'envoi de mail
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-15 16:31:11 +00:00
b03a4214c0 Bug: Validation des conges
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-11 15:33:04 +00:00
d4f4b7d954 Bug: Validation des conges
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-11 15:27:50 +00:00
489eeb439f Correction Bug : Validation 2026-05-11 14:46:59 +00:00
5277b7f355 Bug: Validation de reservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-11 13:20:13 +00:00
9d9e6c6549 Bug: Affichage de la lite des conges en fonction des profil
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-07 15:32:22 +00:00
784822478e feature: Affichage de la liste des bailleur 2026-05-07 15:28:52 +00:00
ab87baaa3a Bug: Affichage des details de la reservation 2026-05-07 15:28:42 +00:00
6026af5498 Bug : Affichages des conges 2026-05-07 15:23:47 +00:00
91bdd791f1 Bug : Affichage des demandes de conges 2026-05-07 15:11:25 +00:00
671072864d Bug: Details des reservations
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-07 14:44:16 +00:00
bb93a853db Gestion de l'envoie des mails
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-07 11:25:11 +00:00
99434a21e0 Fonctionnalite: verifier qu'aucun contrat n'est actif avant la creation d'un nouveau pour un employe
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-06 16:01:37 +00:00
f6b90e7dd0 Verification du statut avant la creation d'un contrat 2026-05-06 15:53:02 +00:00
fcfac71026 clean: remove pycache from tracking 2026-05-06 15:44:16 +00:00
50 changed files with 368 additions and 156 deletions

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@ venv/
media/
staticfiles/
.env
migrations/
migrations/
*.pyc

2
Jenkinsfile vendored
View File

@@ -39,6 +39,8 @@ pipeline
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"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -150,6 +150,7 @@ MEDIA_ROOT = BASE_DIR / "media"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Configuration de l'email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'ssl0.ovh.net'
EMAIL_PORT = 465

View File

@@ -1,5 +1,5 @@
from django.utils import timezone
from gestion_employe.models import Contrat
from gestion_employe.models import Contrat, Employe
from gestion_conge.models import Conge
@@ -7,6 +7,7 @@ QUOTA_CONGE_ANNUEL = 30
NOMBRE_PAGINATION = 8
DEBUT_RAPPEL = 60
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):
"""Fonction de calcul du solde de congé restant l'employé"""
@@ -31,4 +32,26 @@ def solde_conge(employe):
"success": True,
"quota_annuel": QUOTA_CONGE_ANNUEL - 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))

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -24,7 +24,6 @@ enregistrerProfil.addEventListener("click", (e) => {
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
@@ -32,6 +31,10 @@ enregistrerProfil.addEventListener("click", (e) => {
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
const photoInput = $("photo");
if (photoInput.files.length > 0) {
formData.append("photo", photoInput.files[0]);
}
fetch(url, {
method: "POST",

View File

@@ -1,5 +1,5 @@
import json
from datetime import timedelta, datetime
from datetime import date, timedelta, datetime
from dateutil.relativedelta import relativedelta
from django.utils import timezone
@@ -109,6 +109,13 @@ 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}.")
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')
else:
messages.error(request, "Erreur : Formulaire non valide.")
@@ -184,7 +191,7 @@ def modifier_employer(request):
except Employe.DoesNotExist:
return JsonResponse({"message": "Employé non trouvé."})
if request.method == "POST":
data = json.loads(request.body)
data = request.POST
user = User.objects.get(username=request.user)
user.last_name = data['nom']
user.first_name = data['prenom']
@@ -247,14 +254,25 @@ def suppression_affectation(request):
return JsonResponse({"message": "Affectation supprimée avec succès."})
def creation_contrat(request):
"""Vue pour permettre à un utilisateur de créer un contrat pour un employé"""
"""Créer un contrat pour un employé (avec contrôle d'existence de contrat actif)"""
try:
employe = Employe.objects.get(id=request.POST.get('employe_id'))
except Employe.DoesNotExist:
messages.error(request, "Employé non trouvé.")
return redirect('employe-index')
contrat_actif = Contrat.objects.filter(
employe=employe,
date_fin__gte=date.today()
).exists()
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)
if form.is_valid():
contrat = form.save(commit=False)
@@ -263,9 +281,13 @@ def creation_contrat(request):
messages.success(request, "Contrat créé avec succès.")
return redirect('gestion_employe:index')
messages.error(request, "Formulaire non valide")
else:
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
def enregistrer_detail_employe(request):
@@ -413,4 +435,4 @@ def supprimer_formation(request, id_formation):
if request.method == "POST":
formation.delete()
messages.success(request, "Formation supprimée ")
return redirect("mes_formations")
return redirect("mes_formations")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,5 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
let table;
btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur');
@@ -20,4 +21,31 @@ btnEnregistrerBailleur.addEventListener('click', function() {
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,20 +1,51 @@
<div class="modal fade" id="modalBailleur" tabindex="-1" aria-labelledby="modalBailleurLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog ">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalBailleurLabel">Ajouter un Bailleur</h5>
<h5 class="modal-title" id="modalBailleurLabel">Gestion des Bailleurs</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form id="formBailleur" method="POST" action="{% url 'gestion_projet:creation-bailleur' %}">
{% csrf_token %}
{{ form_ajout_bailleur.as_p }}
</form>
</div>
<div class="modal-footer">
<button type="submit" id="btnEnregistrerBailleur" class="btn btn-success"><i class="bi bi-save me-1"></i> Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
<ul class="nav nav-tabs px-3 pt-2" id="bailleurTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="ajouter-tab" data-bs-toggle="tab"
data-bs-target="#ajouter" type="button" role="tab">
Ajouter
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="liste-tab" data-bs-toggle="tab"
data-bs-target="#liste" type="button" role="tab">
Liste
</button>
</li>
</ul>
<div class="modal-body">
<div class="tab-content pt-3">
<div class="tab-pane fade show active" id="ajouter" role="tabpanel">
<form id="formBailleur" method="POST" action="{% url 'gestion_projet:creation-bailleur' %}">
{% 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>

View File

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

View File

@@ -143,6 +143,22 @@ def creation_bailleur(request):
return JsonResponse({'success': True})
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
def ajouter_financement_projet(request):
"""Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%"""
@@ -318,6 +334,7 @@ def activites_projet(request):
}
return render(request, 'gestion_projet/suivi_activite.html', context)
@login_required
def ajouter_activite_projet(request):
"""Vue pour ajouter une activité à un projet spécifique via un formulaire"""

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -15,14 +15,14 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
.then(response => response.json())
.then(data => {
currentReservationId = data.id_reservation;
console.log(data);
$("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation;
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
$("date_evenement").value=data.date_evenement;
$("date_debut").value=data.date_debut;
$("date_fin").value=data.date_fin;
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
@@ -30,11 +30,10 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom;
if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none";
}
})
}
});
@@ -80,6 +79,11 @@ $("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(
url_annuler,
{
@@ -130,6 +134,9 @@ if($("refuserReservation")){
const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value;
console.log(idRes);
e.preventDefault();
fetch(url, {
method: "POST",
headers: {
@@ -161,16 +168,11 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData();
console.log(data);
if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none';
}
if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
const id_user = $("current-user-id").dataset.userid;
$("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id;
@@ -178,14 +180,18 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
$("date_evenement").value=data.date_debut;
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi;
$("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"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();

View File

@@ -26,9 +26,9 @@
<div class="form-group col-5 me-2">
<label>Selectionner une salle :</label>
<select class = "form-select" id="liste-salle">
<option value='formation'>Salle de formation</option>
<option value='reunion'>Salle de réunion</option>
<option value='lien_zoom'>Lien Zoom</option>
<option value='Salle de formation'>Salle de formation</option>
<option value='Salle de réunion'>Salle de réunion</option>
<option value='Lien Zoom'>Lien Zoom</option>
</select>
</div>
</div>

View File

@@ -1,4 +1,5 @@
<!-- 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-dialog">
<div class="modal-content">
@@ -21,8 +22,12 @@
<input class="form-control" id="salle" readonly>
</div>
<div class="form-group mb-2">
<label>Date de l'évènement :</label>
<input type='date' class="form-control" id="date_evenement" readonly >
<label>Date de debut :</label>
<input type='date' class="form-control" id="date_debut" 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 class="form-group mb-2">
<label>Heure de début :</label>
@@ -48,24 +53,19 @@
<label label="form-check-label">Besoin d'un ordinateur</label>
<input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly >
</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'>
{% if appartient_au_departement_informatique %}
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button>
{% endif %}
{% if appartient_direction and reservation.statut == "en_attente" %}
{% if user|has_group:'direction' %}
<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>
{% endif %}
<span id="current-user-id" data-userid="{{ request.user.id }}"></span>
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,74 +1,111 @@
import json
from datetime import timedelta
import json
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
from django.http import JsonResponse, HttpRequest
from django.forms import model_to_dict
from fonction_utilitaire import fonctions_utilitaire
from gestion_employe.models import Employe
from gestion_salle.forms import ReservationForm
from .models import Reservation
@login_required
def index(request:HttpRequest):
"""Vue de gestion de la reservation de la salle"""
def index(request: HttpRequest):
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
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.")
messages.error(request, "Profil employé introuvable.")
return redirect('gestion_conges:conge')
if request.method == "POST":
form = ReservationForm(request.POST)
if form.is_valid():
date_debut = form.cleaned_data.get('date_debut')
date_fin = form.cleaned_data.get('date_fin')
salle = form.cleaned_data.get('salle')
heure_debut = form.cleaned_data.get('heure_debut')
heure_fin = form.cleaned_data.get('heure_fin')
motif_reservation = form.cleaned_data.get('motif_reservation')
besoin_zoom = form.cleaned_data.get('besoin_zoom')
besoin_ordi = form.cleaned_data.get('besoin_ordi')
while date_debut <= date_fin :
reservation = Reservation(
employe = employe,
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,
)
reservation.save()
date_debut = date_debut + timedelta(days=1)
messages.success(request, "Réservation(s) créées avec succès.")
if form.is_valid():
date_debut = form.cleaned_data['date_debut']
date_fin = form.cleaned_data['date_fin']
salle = form.cleaned_data['salle']
heure_debut = form.cleaned_data['heure_debut']
heure_fin = form.cleaned_data['heure_fin']
motif = form.cleaned_data['motif_reservation']
besoin_zoom = form.cleaned_data['besoin_zoom']
besoin_ordi = form.cleaned_data['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():
messages.error(
request,
"Veuillez renseigner votre nom et prénom pour pouvoir faire une réservation."
)
return redirect('gestion_salle:reservation-salle')
created = []
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')
formulaire_reservation = ReservationForm()
departement = Employe.objects.get(user__username=request.user).departement
appartient_direction = 'direction' in request.user.groups.values_list('name', flat=True)
liste_demande_reservation = [
reservation.id for reservation in
Reservation.objects.filter(employe=employe, statut='en_attente')
]
departement = employe.departement
appartient_direction = request.user.groups.filter(name='direction').exists()
liste_demande_reservation = Reservation.objects.filter(
employe=employe,
statut='en_attente'
).values_list('id', flat=True)
context = {
'formulaire_reservation': formulaire_reservation,
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
'appartient_au_departement_informatique': 'Informatique' == departement.nom if departement else False,
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique",
'appartient_direction': appartient_direction,
'liste_demande_reservation': liste_demande_reservation
'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:
color = None
if reservation.statut == "en_attente":
@@ -78,6 +115,8 @@ 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),
@@ -86,7 +125,6 @@ def liste_reservation(request:HttpRequest):
"end": reservation.heure_fin,
"color": color,
})
return JsonResponse(liste_reservation, safe=False)
@login_required
@@ -96,28 +134,33 @@ def liste_reservation_attente(request):
{
**model_to_dict(reservation),
"employe": f"{reservation.employe.user.first_name} {reservation.employe.user.last_name}",
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle)
"employe_id": reservation.employe.user.id,
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
'statut': dict(Reservation.STATUT).get(reservation.statut),
} for reservation in reservations
]
return JsonResponse(liste_reservation, safe=False)
def detail_reservation(request:HttpRequest, reservation_id:int):
reservation = Reservation.objects.get(id=reservation_id)
employe = reservation.employe.user
reservation_json = {
'id_reservation': reservation_id,
'employe': f"{employe.first_name} {employe.last_name}",
'salle': reservation.salle,
'statut': reservation.statut,
'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'),
'statut': dict(Reservation.STATUT).get(reservation.statut),
'date_debut': reservation.date_debut.strftime('%Y-%m-%d'),
'date_fin': reservation.date_fin.strftime('%Y-%m-%d'),
'heure_debut': reservation.heure_debut.strftime('%H:%M'),
'heure_fin': reservation.heure_fin.strftime('%H:%M'),
'motif_reservation': reservation.motif_reservation,
'besoin_zoom': reservation.besoin_zoom,
'besoin_ordinateur': reservation.besoin_ordi,
'lien_zoom': reservation.lien_zoom or '',
'motif_refus': reservation.motif_refus or '',
}
return JsonResponse(reservation_json, safe=True)
@@ -165,31 +208,48 @@ def annuler_reservation(request:HttpRequest):
return redirect('gestion_salle:reservation-salle')
@login_required
def valider_reservation(request:HttpRequest):
"""Vue de gestion de l'annulation de la reservation"""
def valider_reservation(request: HttpRequest):
"""Validation d'une réservation"""
if request.method == 'POST':
reservation_id= request.POST['id_reservation']
reservation_id = request.POST.get('id_reservation')
try:
reservation = Reservation.objects.get(id=reservation_id)
except reservation.DoesNotExist:
messages.error(request, "La resevertion selectionné n'existe pas.")
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")
reservation.statut = 'validee'
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')
@login_required
def refuser_reservation(request:HttpRequest):
"""Vue de gestion de refus de la reservation"""
data = json.loads(request.body)
reservation_id = data.get("id_reservation")
try:
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist and ValueError:
return JsonResponse({"message": "La resevertion selectionné n'existe pas."})
else:
reservation.statut = "refusee"
def refuser_reservation(request: HttpRequest):
"""Refuser une réservation"""
if request.method == 'POST':
reservation_id = json.loads(request.body).get('id_reservation')
try:
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist:
messages.error(request, "La réservation n'existe pas.")
return JsonResponse({"message": "Une erreur s'est produite lors de l'annulation de la reservation."})
reservation.statut = 'refusee'
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."})

View File

@@ -190,4 +190,4 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
modal.show();
})
})