1 Commits

Author SHA1 Message Date
Fatimac2804
7b3d8cdc8e Affichage du message d'alerte sur l'etat du contrat de l'employé
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-09-08 17:02:58 +02:00
69 changed files with 2874 additions and 586 deletions

Binary file not shown.

View File

@@ -15,21 +15,6 @@ try:
except: except:
pass 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): def solde_conge(employe):
"""Fonction de calcul du solde de congé restant l'employé""" """Fonction de calcul du solde de congé restant l'employé"""
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first() contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()

View File

@@ -2,34 +2,6 @@ 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"},
@@ -49,31 +21,12 @@ 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'));
@@ -91,11 +44,6 @@ 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;
@@ -111,10 +59,6 @@ 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,13 +83,6 @@
</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,9 +24,4 @@ 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

@@ -235,55 +235,3 @@ def validation_de_conge(request):
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

@@ -9,6 +9,7 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
{"title": "Projet", "field": "projet"}, {"title": "Projet", "field": "projet"},
{"title": "Téléphone", "field": "telephone"}, {"title": "Téléphone", "field": "telephone"},
], ],
// ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
responsiveLayout : "hide" responsiveLayout : "hide"
@@ -18,6 +19,7 @@ fetch(url_liste_employe)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success){ if (data.success){
console.log(data.data)
tableau_liste_employe.setData(data.data); tableau_liste_employe.setData(data.data);
}else{ }else{
alert(data.message); alert(data.message);
@@ -27,6 +29,7 @@ fetch(url_liste_employe)
tableau_liste_employe.on("rowClick", function (row, rowData) { tableau_liste_employe.on("rowClick", function (row, rowData) {
const data = rowData.getData(); const data = rowData.getData();
console.log('Employé cliqué:', data);
document.getElementById('detail-id').value = data.id; document.getElementById('detail-id').value = data.id;
document.getElementById('detail-matricule').value = data.matricule; document.getElementById('detail-matricule').value = data.matricule;
@@ -212,9 +215,16 @@ $("enregistrerDetail").addEventListener("click", function() {
const matricule = document.getElementById('detail-matricule').value; const matricule = document.getElementById('detail-matricule').value;
const departement = document.getElementById('detail-departement').value; const departement = document.getElementById('detail-departement').value;
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url; console.log('=== ENREGISTREMENT DÉTAILS ===');
console.log('ID:', id_);
console.log('Fonction:', fonction);
console.log('Date embauche:', dateEmbauche);
console.log('Matricule:', matricule);
console.log('Département (valeur brute):', departement);
console.log('Département (type):', typeof departement);
console.log('Département est vide:', !departement);
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset) const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
const payload = { const payload = {
"id": id_, "id": id_,
@@ -224,6 +234,9 @@ $("enregistrerDetail").addEventListener("click", function() {
"departement": departement "departement": departement
}; };
console.log('Payload JSON:', payload);
console.log('Payload stringifié:', JSON.stringify(payload));
fetch(url_enregistrer_detail, { fetch(url_enregistrer_detail, {
method: "POST", method: "POST",
headers: { headers: {
@@ -270,12 +283,15 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants, ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
}) })
/* let tabContrats = null; let tabContrats = null;
document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () { document.getElementById("modalContratsEmploye")
.addEventListener("show.bs.modal", function () {
const employeId = document.getElementById("detail-id").value; const employeId = document.getElementById("detail-id").value;
console.log("Employe ID :", employeId);
if (!employeId) { if (!employeId) {
alert("Aucun employé sélectionné"); alert("Aucun employé sélectionné");
return; return;
@@ -334,4 +350,4 @@ document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal"
.catch(error => { .catch(error => {
console.error("Erreur :", error); console.error("Erreur :", error);
}); });
}); */ });

View File

@@ -12,22 +12,27 @@
</div> </div>
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show mt-2" role="alert"> <div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }} {{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if not expiration_contrat %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
Les informations sur votre contrat n'ont pas été renseignées,
veuillez contacter les ressources humaines. </div>
{% if not has_contrat %} {% elif contrat_nb_jours_restant > 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
<div class="alert alert-danger mt-2"> Votre contrat de travail expire dans <strong>{{ contrat_nb_jours_restant }} jour{{ contrat_nb_jours_restant|pluralize }}</strong>,
<strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines. veuillez contacter les ressources humaines. </div>
</div>
{% elif expiration_contrat %} {% elif contrat_nb_jours_restant == 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
<div class="alert alert-warning mt-2"> Votre contrat de travail <strong>expire aujourd'hui</strong>.
<strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines. Veuillez contacter les ressources humaines. </div>
</div>
{% elif contrat_nb_jours_restant < 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
Votre contrat de travail est <strong>déjà expiré</strong>.
Veuillez contacter les ressources humaines. </div>
{% endif %} {% endif %}
<div class="accordion mt-2" id="accordionInformationEmploye"> <div class="accordion mt-2" id="accordionInformationEmploye">
@@ -127,7 +132,7 @@
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Statut :</label> <label>Statut :</label>
<input type="text" class="form-control" value="{{ contrat.statut }}" id="satut" readonly> <input type="text" class="form-control" value="{{ contrat.statut }}" id="statut" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>fichier :</label> <label>fichier :</label>

View File

@@ -8,7 +8,7 @@
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body" id="modalDetailEmployeBody" data-url="{% url 'gestion_employe:enregistrer-detail-employe' %}"> <div class="modal-body" id="modalDetailEmployeBody">
<div class="row g-3"> <div class="row g-3">
<input type="hidden" id="detail-id"> <input type="hidden" id="detail-id">
<div class="col-md-6"> <div class="col-md-6">

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,18 +27,6 @@ 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()
@@ -353,6 +341,9 @@ def enregistrer_detail_employe(request):
# Traiter le département # Traiter le département
dept_value = data.get('departement') dept_value = data.get('departement')
print(f"[DEBUG] Département brut: {dept_value!r}")
print(f"[DEBUG] Type département: {type(dept_value).__name__}")
print(f"[DEBUG] Est vide: {not dept_value}")
if dept_value and str(dept_value).strip(): if dept_value and str(dept_value).strip():
try: try:
@@ -530,7 +521,7 @@ def modifier_mot_passe(request):
} }
response = requests.post( response = requests.post(
f"{config('SERVEUR_AUTHENTIFICATION')}modifier-mot-de-passe/", "http://localhost:8000/modifier-mot-de-passe/",
json=donnees, json=donnees,
headers=headers headers=headers
) )

View File

@@ -22,6 +22,7 @@ class ProjetForm(forms.ModelForm):
'date_fin', 'date_fin',
'numero_convention', 'numero_convention',
'domaine_recherche', 'domaine_recherche',
'type_projet',
'budget', 'budget',
'budget_RH', 'budget_RH',
'description' 'description'
@@ -36,6 +37,7 @@ class ProjetForm(forms.ModelForm):
'nom_projet': forms.TextInput(attrs={'class': "form-control"}), 'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
'numero_convention': forms.TextInput(attrs={'class': "form-control"}), 'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}), 'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
'type_projet': forms.Select(attrs={'class': "form-select"}),
'budget': forms.NumberInput(attrs={'class': "form-control"}), 'budget': forms.NumberInput(attrs={'class': "form-control"}),
'budget_RH': forms.NumberInput(attrs={'class': "form-control"}), 'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
'description': forms.Textarea(attrs={'class': "form-control"}), 'description': forms.Textarea(attrs={'class': "form-control"}),

View File

@@ -1,3 +1,5 @@
from datetime import datetime
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@@ -31,11 +33,9 @@ class DomaineDeRecherche(models.Model):
DOMAINE_RECHERCHE = ( DOMAINE_RECHERCHE = (
('sciences_sociales', 'Sciences sociales'), ('sciences_sociales', 'Sciences sociales'),
('naturelles', 'Naturelles'),
('humaines', 'Humaines'), ('humaines', 'Humaines'),
('epidemiologie', 'Epidémiologie'), ('veterinaires', 'Vétérinaires')
('sciences_biomedicales', 'Sciences biomédicales'),
('service_pedagogique', 'Service Pédagogique'),
('recherche_clinique', 'Recherche clinique')
) )
nom = models.CharField( nom = models.CharField(
@@ -54,6 +54,13 @@ class DomaineDeRecherche(models.Model):
class Projet(models.Model): class Projet(models.Model):
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé.""" """Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
TYPE_PROJET = (
('laboratoire', 'Laboratoire'),
('épidémiologie', 'Épidémiologie'),
('sciences sociales', 'Sciences sociales'),
('cliniques', 'Cliniques'),
('autre', 'Autre'),
)
id_projet = models.CharField( id_projet = models.CharField(
max_length=100, max_length=100,
@@ -79,6 +86,12 @@ class Projet(models.Model):
description = models.TextField( description = models.TextField(
verbose_name="Description" verbose_name="Description"
) )
type_projet = models.CharField(
max_length=100,
choices=TYPE_PROJET,
default='épidémiologie',
verbose_name="Type de projet"
)
domaine_recherche = models.ManyToManyField(DomaineDeRecherche) domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
budget=models.DecimalField( budget=models.DecimalField(
max_digits=12, max_digits=12,
@@ -101,13 +114,13 @@ class Projet(models.Model):
@property @property
def statut(self): def statut(self):
if self.date_fin < timezone.now().date(): if self.date_fin < datetime.date.today():
return "Terminé" return "Terminé"
return "En cours" return "En cours"
@property @property
def avancement(self): def avancement(self):
aujourd_hui = timezone.now().date() aujourd_hui = datetime.date.today()
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin): if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
duree_projet = (self.date_fin - self.date_debut).days duree_projet = (self.date_fin - self.date_debut).days
temps_ecoule = (aujourd_hui - self.date_debut).days temps_ecoule = (aujourd_hui - self.date_debut).days

View File

@@ -1,6 +1,5 @@
const $ = (element) => document.getElementById(element) const $ = (element) => document.getElementById(element)
const url_liste_projet = $("tableau-liste-projet").dataset.url; const url_liste_projet = $("tableau-liste-projet").dataset.url;
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", { const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
fitColumns: true, fitColumns: true,
responsiveLayout : true, responsiveLayout : true,
@@ -40,6 +39,7 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
$("detail_date_debut").value = data.date_debut; $("detail_date_debut").value = data.date_debut;
$("detail_date_fin").value = data.date_fin; $("detail_date_fin").value = data.date_fin;
$("detail_numero_convention").value = data.numero_convention; $("detail_numero_convention").value = data.numero_convention;
$("detail_type_projet").value = data.type_projet;
Array.from($("detail_domaine_recherche").options).forEach(option => { Array.from($("detail_domaine_recherche").options).forEach(option => {
if (data.domaine_recherche.includes(option.value)) { if (data.domaine_recherche.includes(option.value)) {
option.selected = true; option.selected = true;

View File

@@ -31,15 +31,23 @@
<label>Date de fin :</label> <label>Date de fin :</label>
<input type="date" class="form-control" id="detail_date_fin" name='date_fin'> <input type="date" class="form-control" id="detail_date_fin" name='date_fin'>
</div> </div>
<div class="form-group mb-2">
<label> Type de projet </label>
<select class="form-select" id="detail_type_projet" name='type_projet'>
<option value="laboratoire">Laboratoire</option>
<option value="épidémiologie">Épidémiologie</option>
<option value="sciences sociales">Sciences sociales</option>
<option value="cliniques">Cliniques</option>
<option value="autre">Autre</option>
</select>
</div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label> Domaine de recherche </label> <label> Domaine de recherche </label>
<select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche"> <select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche">
<option value="sciences_sociales">Sciences sociales</option> <option value="sciences_sociales">Sciences sociales</option>
<option value="naturelles">Naturelles</option>
<option value="humaines">Humaines</option> <option value="humaines">Humaines</option>
<option value="epidemiologie">Epidémiologie</option> <option value="veterinaires">Vétérinaires</option>
<option value="sciences_biomedicales">Sciences biomédicales</option>
<option value="service_pedagogique">Service Pédagogique</option>
<option value="recherche_clinique">Recherche clinique</option>
</select> </select>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">

View File

@@ -44,6 +44,7 @@ def liste_projet(request):
"date_fin": p.date_fin, "date_fin": p.date_fin,
"numero_convention": p.numero_convention, "numero_convention": p.numero_convention,
"description": p.description, "description": p.description,
"type_projet": p.type_projet,
"budget": p.budget, "budget": p.budget,
"budget_RH": p.budget_RH, "budget_RH": p.budget_RH,
"created_at": p.created_at, "created_at": p.created_at,

View File

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

View File

@@ -92,6 +92,9 @@ $("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(
@@ -186,7 +189,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;
@@ -203,6 +206,8 @@ 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,15 @@
<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-center mt-2'> <div class='d-flex justify-content-around mt-2'>
{% if est_equipe_informatique %} {% if appartient_au_departement_informatique %}
<button class="btn btn-primary me-1" id="ajoutZoom">Ajout du lien zoom</button> <button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button>
{% endif %} {% endif %}
{% if est_assistante_direction %} {% if appartient_direction %}
<button class="btn btn-danger me-1" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button> <button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
<button class="btn btn-success me-1" id="bouton-valider">Valider</button> <button class="btn btn-success" id="bouton-valider">Valider</button>
{% endif %} {% endif %}
{% if est_equipe_informatique %} <span id="current-user-id" data-user-id="{{ request.user.id }}"></span>
<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>

View File

@@ -3,7 +3,6 @@ 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
@@ -17,16 +16,13 @@ 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)
@@ -41,11 +37,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, "La date de fin est antérieure à la date de début.") messages.error(request, "Date fin invalide.")
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, "L'heure de fin est antérieure à l'heure de début.") messages.error(request, "Heure invalide.")
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():
@@ -58,9 +54,8 @@ def index(request: HttpRequest):
created = [] created = []
current_date = date_debut current_date = date_debut
print(f"Création de réservations pour {employe}")
while current_date <= date_fin: 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,
@@ -79,35 +74,18 @@ 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.")
# Nouvelle logique d'envoi de mails à la création : if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
# 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="Demande de réservation de salle", sujet = "Reservation de salle",
message=f"""Bonjour, message = f"""
Une nouvelle demande de réservation de la {dict(Reservation.TYPE_CHOICES).get(salle)} a été effectuée par {employe.user.first_name} {employe.user.last_name} du {form.cleaned_data.get('date_debut').strftime('%d/%m/%Y')} au {form.cleaned_data.get('date_fin').strftime('%d/%m/%Y')} pour motif "{motif}". Une nouvelle demande de réservation de la {dict(Reservation.TYPE_CHOICES).get(salle)} a été effectuée par {employe.user.first_name} {employe.user.last_name} du {form.cleaned_data.get('date_debut').strftime('%d/%m/%Y')} au {form.cleaned_data.get('date_fin').strftime('%d/%m/%Y')} pour motif "{motif}".
{"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.""", 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') 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,
@@ -117,9 +95,8 @@ Veuillez vous connecter à la plateforme pour traiter cette demande.""",
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': est_equipe_info, 'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique",
'est_assistante_direction': est_assistante, 'appartient_direction': request.user.groups.filter(name='direction').exists(),
'est_equipe_informatique': est_equipe_info,
'liste_demande_reservation': list(liste_demande_reservation), 'liste_demande_reservation': list(liste_demande_reservation),
} }
@@ -131,6 +108,7 @@ 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"
@@ -139,6 +117,8 @@ 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),
@@ -151,35 +131,7 @@ 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),
@@ -215,99 +167,49 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
@login_required @login_required
def ajouter_lien_zoom(request:HttpRequest): def ajouter_lien_zoom(request:HttpRequest):
"""Traitement de la réservation par l'équipe informatique : """Vue de gestion de l'ajout du lien zoom"""
- 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': if request.method == 'POST':
reservation_id = request.POST.get('id_reservation') reservation_id= request.POST['id_reservation']
try: try:
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
except (Reservation.DoesNotExist, ValueError): except reservation.DoesNotExist:
messages.error(request, "La réservation sélectionnée n'existe pas.") messages.error(request, "La resevertion selectionné n'existe pas.")
return redirect('gestion_salle:reservation-salle') return redirect("salle")
try: if not (reservation.besoin_zoom or reservation.salle == "lien_zoom"):
employe = Employe.objects.get(user=request.user) messages.error(request, "Cette réservation ne nécessite pas de lien Zoom.")
except Employe.DoesNotExist: return redirect('gestion_salle:reservation-salle')
messages.error(request, "Profil employé introuvable.") elif reservation.statut in ["annulee", "refusee"]:
return redirect('gestion_salle:reservation-salle') 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')
# 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
if request.method == "POST":
lien = request.POST.get("lien_zoom")
reservation.lien_zoom = lien
reservation.statut = "validee" reservation.statut = "validee"
reservation.save() reservation.save()
messages.success(request, "La demande a été traitée et validée. ") messages.success(request, "Le lien Zoom a été ajouté et la réservation a été 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, réservée à l'employé demandeur ou aux validateurs.""" """Vue de gestion de l'annulation de la reservation"""
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 reservation selectionné n'existe pas.") messages.error(request, "La resevertion selectionné n'existe pas.")
return redirect("gestion_salle:reservation-salle") return redirect("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, "La réservation a été annulée.") messages.success(request, "Votre 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 selon le profil : """Validation d'une réservation"""
- 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')
@@ -315,122 +217,37 @@ 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('gestion_salle:reservation-salle') return redirect("salle")
try: reservation.statut = 'validee'
employe = Employe.objects.get(user=request.user) reservation.save()
except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.")
return redirect('gestion_salle:reservation-salle')
est_assistante = fonctions_utilitaire.est_assistante_direction(employe) fonctions_utilitaire.envoyer_mail(
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe) 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]
)
if est_assistante: messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} validée avec succès.")
# 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, autorisée à l'assistant de direction """Vue de gestion de refus de la reservation"""
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 reservation selectionné n'existe pas."}) return JsonResponse({"message": "La resevertion selectionné n'existe pas."})
else:
reservation.statut = "refusee"
reservation.save()
try: fonctions_utilitaire.envoyer_mail(
employe = Employe.objects.get(user=request.user) sujet = "Reservation de salle",
except Employe.DoesNotExist: 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.""",
return JsonResponse({"message": "Profil employé introuvable."}) destinataires = [reservation.employe.user.email]
)
est_assistante = fonctions_utilitaire.est_assistante_direction(employe) return JsonResponse({"message": "Réservation refusée avec succès."})
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."})

6
staticfiles/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class GestionCongeConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'gestion_conge'

View File

@@ -3,6 +3,23 @@ const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url const url_liste_conge_attente = $("liste-demande-conges").dataset.url
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// layout : "fitColumns",
=======
layout : "fitColumns",
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
layout : "fitColumns",
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
layout : "fitColumns",
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
// layout : "fitColumns",
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
columns: [ columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"}, {"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{ {"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
@@ -14,7 +31,23 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
outputFormat:"dd/MM/yy", outputFormat:"dd/MM/yy",
}}, }},
{"title": "Type de congé", "field": "type"}, {"title": "Type de congé", "field": "type"},
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
{"title": "Date de la demande", "field": "date_demande", responsive: 1}, {"title": "Date de la demande", "field": "date_demande", responsive: 1},
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
{"title": "Date de la demande", "field": "date_demande", responsive: 1},
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{ {"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
allowEmpty : true , allowEmpty : true ,
}}, }},
@@ -23,7 +56,23 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}}, }},
], ],
pagination: true, pagination: true,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
paginationSize: 5, paginationSize: 5,
=======
paginationSize: 5
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
paginationSize: 5
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
paginationSize: 5
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
paginationSize: 5,
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
}) })
const bouton_demande_conges = $("bouton-demande-conge"); const bouton_demande_conges = $("bouton-demande-conge");

View File

@@ -9,15 +9,30 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
{"title": "Projet", "field": "projet"}, {"title": "Projet", "field": "projet"},
{"title": "Téléphone", "field": "telephone"}, {"title": "Téléphone", "field": "telephone"},
], ],
// ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
responsiveLayout : "hide" responsiveLayout : "hide"
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
responsiveLayout : "hide"
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
}) })
fetch(url_liste_employe) fetch(url_liste_employe)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success){ if (data.success){
console.log(data.data)
tableau_liste_employe.setData(data.data); tableau_liste_employe.setData(data.data);
}else{ }else{
alert(data.message); alert(data.message);
@@ -27,7 +42,6 @@ fetch(url_liste_employe)
tableau_liste_employe.on("rowClick", function (row, rowData) { tableau_liste_employe.on("rowClick", function (row, rowData) {
const data = rowData.getData(); const data = rowData.getData();
document.getElementById('detail-id').value = data.id; document.getElementById('detail-id').value = data.id;
document.getElementById('detail-matricule').value = data.matricule; document.getElementById('detail-matricule').value = data.matricule;
document.getElementById('detail-employe').value = data.employe; document.getElementById('detail-employe').value = data.employe;
@@ -210,31 +224,23 @@ $("enregistrerDetail").addEventListener("click", function() {
const fonction = document.getElementById('detail-fonction').value; const fonction = document.getElementById('detail-fonction').value;
const dateEmbauche = document.getElementById('detail-dateEmbauche').value; const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
const matricule = document.getElementById('detail-matricule').value; const matricule = document.getElementById('detail-matricule').value;
const departement = document.getElementById('detail-departement').value;
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url;
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset)
const payload = {
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule,
"departement": departement
};
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
fetch(url_enregistrer_detail, { fetch(url_enregistrer_detail, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value "X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
}, },
body: JSON.stringify(payload) body: JSON.stringify({
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule
})
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
console.log('Réponse du serveur:', data);
alert(data.message); alert(data.message);
location.reload(); location.reload();
}) })
@@ -269,69 +275,3 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
], ],
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants, ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
}) })
/* let tabContrats = null;
document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () {
const employeId = document.getElementById("detail-id").value;
if (!employeId) {
alert("Aucun employé sélectionné");
return;
}
fetch(`/employé/employe/${employeId}/contrats/`)
.then(response => {
if (!response.ok) {
throw new Error("Erreur HTTP " + response.status);
}
return response.json();
})
.then(data => {
document.getElementById("nom-employe-contrat").innerText =
data.employe;
if (tabContrats) {
tabContrats.destroy();
}
tabContrats = new Tabulator(
"#table-contrats-tabulator",
{
data: data.contrats,
layout: "fitColumns",
placeholder: "Aucun contrat",
columns: [
{ title: "Numéro", field: "numero_contrat" },
{ title: "Type", field: "type_contrat" },
{ title: "Date début", field: "date_debut" },
{ title: "Date fin", field: "date_fin" },
{ title: "Salaire", field: "salaire_mensuel" },
{ title: "Statut", field: "statut" },
{
title: "Contrat",
field: "fichier",
formatter: function(cell) {
const url = cell.getValue();
if (!url) {
return "<span class='text-danger'>Aucun fichier</span>";
}
return `<a href="${url}" target="_blank" class="btn btn-sm btn-primary">
<i class="bi bi-file-earmark-pdf"></i> Ouvrir
</a>`;
}
}
]
}
);
})
.catch(error => {
console.error("Erreur :", error);
});
}); */

View File

@@ -13,20 +13,22 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
inputFormat:"yyyy-MM-dd", inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy", outputFormat:"dd/MM/yy",
}}, }},
{"title": "Fichier", "field": "certificat", formatter:"link", formatterParams:{
label:"Voir le fichier",
target:"_blank",
}},
], ],
ajaxURL: url_certificat, ajaxURL: url_certificat,
}) })
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
const enregistrerProfil = $("enregistrerProfil"); const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => { enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url; const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value; const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData(); const formData = new FormData();
formData.append("nom", $("nom").value); formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value); formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value); formData.append("email", $("email").value);
@@ -34,10 +36,6 @@ enregistrerProfil.addEventListener("click", (e) => {
formData.append("adresse", $("adresse").value); formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value); formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value); formData.append("date_naissance", $("date_naissance").value);
const photoInput = $("photo");
if (photoInput.files.length > 0) {
formData.append("photo", photoInput.files[0]);
}
fetch(url, { fetch(url, {
method: "POST", method: "POST",
@@ -50,3 +48,52 @@ enregistrerProfil.addEventListener("click", (e) => {
.then(data => alert(data.message)) .then(data => alert(data.message))
.catch(error => console.error("Erreur:", error)); .catch(error => console.error("Erreur:", error));
}); });
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
fetch(url, {
method: "POST",
headers: {
"X-CSRFToken": csrftoken
},
body: formData
})
.then(response => response.json())
.then(data => alert(data.message))
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
})
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
})
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
})
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
.catch(error => console.error("Erreur:", error));
});
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5

View File

@@ -1,5 +1,18 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
let table; let table;
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
let table;
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
btnEnregistrerBailleur.addEventListener('click', function() { btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur'); const form = document.getElementById('formBailleur');
@@ -21,27 +34,49 @@ btnEnregistrerBailleur.addEventListener('click', function() {
alert('Ce bailleur existe déjà dans la base de données.'); alert('Ce bailleur existe déjà dans la base de données.');
} }
}); });
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
}); });
document.addEventListener("DOMContentLoaded", function () {
table = new Tabulator("#table-bailleurs", { table = new Tabulator("#table-bailleurs", {
ajaxURL: "/gestion-projet/bailleurs/", ajaxURL: "/gestion-projet/bailleurs/",
layout: "fitColumns", layout: "fitColumns",
pagination: "local", pagination: "local",
paginationSize: 5, paginationSize: 5,
columns: [ columns: [
{title: "#", formatter: "rownum", width: 60}, {title: "#", formatter: "rownum", width: 60},
{title: "Organisme", field: "nom_organisme"}, {title: "Organisme", field: "nom_organisme"},
{title: "Contact", field: "contact"}, {title: "Contact", field: "contact"},
{title: "Email", field: "email"}, {title: "Email", field: "email"},
{title: "Pays", field: "pays"}, {title: "Pays", field: "pays"},
], ],
rowDblClick: function(e, row) { rowDblClick: function(e, row) {
const data = row.getData(); const data = row.getData();
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) { if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
supprimerBailleur(data.id); supprimerBailleur(data.id);
}
} }
} });
}); });
<<<<<<< HEAD
=======
});
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
});
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
});
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5

View File

@@ -1,6 +1,5 @@
const $ = (element) => document.getElementById(element) const $ = (element) => document.getElementById(element)
const url_liste_projet = $("tableau-liste-projet").dataset.url; const url_liste_projet = $("tableau-liste-projet").dataset.url;
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", { const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
fitColumns: true, fitColumns: true,
responsiveLayout : true, responsiveLayout : true,
@@ -40,6 +39,7 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
$("detail_date_debut").value = data.date_debut; $("detail_date_debut").value = data.date_debut;
$("detail_date_fin").value = data.date_fin; $("detail_date_fin").value = data.date_fin;
$("detail_numero_convention").value = data.numero_convention; $("detail_numero_convention").value = data.numero_convention;
$("detail_type_projet").value = data.type_projet;
Array.from($("detail_domaine_recherche").options).forEach(option => { Array.from($("detail_domaine_recherche").options).forEach(option => {
if (data.domaine_recherche.includes(option.value)) { if (data.domaine_recherche.includes(option.value)) {
option.selected = true; option.selected = true;

View File

@@ -12,14 +12,24 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
modal.show(); modal.show();
fetch (`/gestion-salle/revervation/details/${event.guid}`) fetch (`/gestion-salle/revervation/details/${event.guid}`)
.then(response => { .then(response => response.json())
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
return response.json();
})
.then(data => { .then(data => {
currentReservationId = data.id_reservation; currentReservationId = data.id_reservation;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
console.log(data);
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
console.log(data);
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
console.log(data);
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
$("id_reservation_detail").value = data.id_reservation; $("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation; $("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation; $("id_reservation_zoom").value = data.id_reservation;
@@ -34,18 +44,38 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur; $("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
const zoomContainer = $("lien_zoom_container"); <<<<<<< HEAD
if (!data.besoin_zoom) { <<<<<<< HEAD
zoomContainer.classList.add("d-none"); <<<<<<< HEAD
} else { <<<<<<< HEAD
zoomContainer.classList.remove("d-none");
}
if (data.besoin_zoom === false){ if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none"; $("lien_zoom_container").className = "d-none";
} }
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
}
<<<<<<< HEAD
<<<<<<< HEAD
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none";
}
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
}) })
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
} }
}); });
@@ -87,13 +117,29 @@ document.addEventListener("DOMContentLoaded", function () {
}) })
$("bouton-annuler").addEventListener("click", (e) => { $("bouton-annuler").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler; const url_annuler = $("formulaire-details").dataset.urlannuler;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault(); e.preventDefault();
<<<<<<< HEAD
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
fetch( fetch(
url_annuler, url_annuler,
{ {
@@ -140,15 +186,28 @@ if($("ajoutZoom")){
if($("refuserReservation")){ if($("refuserReservation")){
$("refuserReservation").addEventListener("click", (e) => { $("refuserReservation").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url = e.currentTarget.dataset.lienrefus; const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value; const idRes = $("id_reservation_detail").value;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
console.log(idRes); console.log(idRes);
e.preventDefault(); e.preventDefault();
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
console.log(idRes);
e.preventDefault();
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
fetch(url, { fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@@ -168,7 +227,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
columns: [ columns: [
{title: "Employé", field: "employe"}, {title: "Employé", field: "employe"},
{title: "Salle", field: "salle"}, {title: "Salle", field: "salle"},
{title: "Date de l'evenement", field: "date_evenement"}, {title: "Date de l'evenement", field: "date_debut"},
{title: "Heure de début", field: "heure_debut"}, {title: "Heure de début", field: "heure_debut"},
{title: "Heure de fin", field: "heure_fin"}, {title: "Heure de fin", field: "heure_fin"},
{title: "Motif de reservation", field: "motif_reservation"}, {title: "Motif de reservation", field: "motif_reservation"},
@@ -180,14 +239,52 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => { tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData(); const data = rowData.getData();
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
console.log(data); console.log(data);
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
console.log(data);
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
console.log(data);
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
if(data.besoin_zoom === false){ if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
const id_user = $("current-user-id").dataset.userId; <<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
const id_user = $("current-user-id").dataset.userid;
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
<<<<<<< HEAD
<<<<<<< HEAD
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
const id_user = $("current-user-id").dataset.userid;
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
$("id_reservation_detail").value = data.id; $("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id; $("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id; $("id_reservation_zoom").value = data.id;
@@ -195,25 +292,53 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe; $("employe").value=data.employe;
$("salle").value=data.salle; $("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut; $("statut-reservation").innerHTML=data.statut;
$("date_evenement").value = data.date_evenement; <<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
=======
$("date_evenement").value=data.date_debut;
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
$("date_evenement").value=data.date_debut;
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
$("date_evenement").value=data.date_debut;
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
$("heure_debut").value=data.heure_debut; $("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin; $("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation; $("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom; $("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi; $("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD
const refuserReservation = document.querySelector("#refuserReservation"); <<<<<<< HEAD
const bouton_valider = document.querySelector("#bouton-valider"); <<<<<<< HEAD
<<<<<<< HEAD
if((refuserReservation | bouton_valider) && data.statut === "En attente"){ =======
refuserReservation.style.display = "inline-block"; >>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
bouton_valider.style.display = "inline-block";
}
if (id_user != data.employe_id){ if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none"; $("bouton-annuler").className = "d-none";
} }
<<<<<<< HEAD
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> 6026af5 (Bug : Affichages des conges)
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide(); bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();

2078
staticfiles/icons.css Normal file

File diff suppressed because it is too large Load Diff

278
staticfiles/styles.css Normal file
View File

@@ -0,0 +1,278 @@
/* === Print === */
@media print {
nav, .btn, .sidebar, .form-select, .form-control {
display: none !important;
}
body {
font-size: 12pt;
color: black;
}
table {
border-collapse: collapse;
}
}
@media (max-width: 768px) {
main {
margin-left: 0;
}
}
/* === Body & Container === */
body {
background-color: #e4dccc;
background-attachment: fixed;
}
.dashboard-container {
background-color: #ffffff;
box-shadow: 0 0 10px rgba(0,0,0,0.05);
border-radius: 8px;
padding: 20px;
margin: 30px auto;
max-width: 1200px;
}
.sidebar {
position: fixed;
width: 220px;
}
main {
margin-left: 220px;
}
/* === Sidebar === */
.sidebar {
background: linear-gradient(180deg, #b35400, #993d00); /* dégradé orange foncé */
color: white;
min-height: 100vh; /* prend toute la hauteur */
top: 0;
z-index: 1000;
overflow-y: auto;
padding: 1rem;
}
.sidebar .nav-link {
color: white;
font-weight: 700;
}
.sidebar .nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 5px;
color: white;
}
.sidebar .dropdown-menu {
display: none;
position: relative;
left: 10px;
top: -5px;
border: none;
padding-left: 1rem;
box-shadow: none;
}
.sidebar .dropdown:hover .dropdown-menu {
display: block;
}
.sidebar a {
color: white;
}
.sidebar a:hover {
color: white;
}
/* === Navbar / Logo / Profil === */
.logo {
max-width: 80px;
}
.img-profil {
width: 80px !important;
height: 80px !important;
object-fit: cover;
border-radius: 50%;
}
/* === Cards === */
.card {
border: 1px solid #dee2e6;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
border-radius: 8px;
transition: transform 0.3s ease;
}
.card:hover {
transform: scale(1.02);
}
.card-header {
background-color: #f8f9fa;
font-weight: bold !important;
color: black !important;
font-size: 1rem;
border-bottom: none;
}
.card-body h5 {
color: black !important;
font-weight: bold;
font-size: 2.2rem;
}
/* === Stat cards === */
.stat-card {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(113, 11, 11, 0.1);
}
/* === Employé / Projet details === */
.employe-detail {
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.employe-detail .card-title {
font-size: 1.8rem;
margin-bottom: 0.5rem;
}
.employe-detail p {
margin-bottom: 0.4rem;
font-size: 1rem;
}
.employe-detail a.btn {
font-weight: 500;
}
.employe-card, .projet-card {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
padding: 2.5rem;
max-width: 1400px;
margin: 0 auto;
font-size: 1.1rem;
}
/* === Forms === */
.form-wrapper {
max-width: 1000px;
margin: auto;
background-color: rgba(255, 255, 255, 0.95);
}
form label {
font-weight: 600;
margin-top: 1rem;
display: block;
}
form input, form select, form textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.3rem;
border-radius: 5px;
border: 1px solid #ccc;
}
/* === Formation badges === */
.formation-badge {
position: relative;
display: inline-block;
}
.formation-actions {
position: absolute;
top: -5px;
right: -5px;
display: none;
}
.formation-badge:hover .formation-actions {
display: inline-block;
}
.formation-actions button {
background: none;
border: none;
color: #fff;
margin-left: 5px;
font-size: 0.9rem;
}
/* === Context menu === */
#contextMenu {
width: 150px;
}
/* === Transitions / Animations === */
.fade-out {
opacity: 0;
transition: opacity 0.5s ease;
}
#main-content {
transition: opacity 0.3s ease;
}
.login-box {
background: linear-gradient(to right, #f5f7fa, #c3cfe2);
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
width: 100%;
max-width: 400px;
}
/* === Responsive === */
@media (max-width: 576px) {
.modal-dialog {
max-width: 100%;
margin: 0;
height: 100%;
}
.modal-content {
height: 100%;
border-radius: 0;
}
.modal-body {
overflow-y: auto;
}
.modal-footer button {
width: 100%;
}
.img-profil {
width: 50px;
height: 50px;
}
.sidebar {
width: 100%;
padding: 0.5rem 1rem;
}
.sidebar .nav-link {
font-size: 0.9rem;
padding: 0.5rem 1rem;
}
}