87 Commits

Author SHA1 Message Date
54dc65f0d8 Feat: Envoie de mail à l'équipe informatique après reservation de salle necessitant un lien zoom ou ordinateur
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-14 10:42:12 +00:00
Fatimac2804
29a42ee6be feat:Ajout notification email équipe informatique
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-09-10 12:23:45 +02:00
904954c735 Fix: Modification de mot de passe
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-08 11:09:53 +00:00
99e764ab4d Merge branch 'main' of https://jenkins.cerfig.org/CERFIG/sirh 2026-09-07 12:10:34 +00:00
0eb745d5f3 Fix: Correction de l'affichage des dates dans les détails de la réservation 2026-09-07 12:07:58 +00:00
097648c03e Fix: Correction de l'affichage des dates dans les détails de la réservation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-07 11:33:21 +00:00
1a21998c39 Ajout de la barre de navigation
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-04 12:19:27 +00:00
900343a4f8 Merge branch 'main' of https://jenkins.cerfig.org/CERFIG/sirh 2026-09-03 14:34:09 +00:00
3c5c58b5d1 Mise a jour: Integration de toutes les suggestions passées 2026-09-03 14:08:01 +00:00
8b9e73f83b Bug: Correction du refus des reservations et d'envoi de mail
All checks were successful
SGL/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/head This commit looks good
2026-09-01 10:04:09 +00:00
8f822fbe85 Bug: Validation de reservation 2026-09-01 09:59:50 +00:00
411f2c43e3 Bug: Affichage des details de la reservation 2026-09-01 09:49:39 +00:00
7826f517f5 Bug : Affichages des conges 2026-08-31 16:02:54 +00:00
0936d6ad02 Bug : Affichage des demandes de conges 2026-08-31 15:54:05 +00:00
472774d78c Bug: Details des reservations 2026-08-31 15:48:22 +00:00
d5a0f1104e Gestion de l'envoie des mails 2026-08-31 15:46:29 +00:00
b9d6ab1186 Verification du statut avant la creation d'un contrat 2026-08-31 15:41:45 +00:00
620a01ec27 clean: remove pycache from tracking 2026-08-31 15:26:41 +00:00
f672b9e9ba Prise en charge de la photo de profil 2026-08-31 15:18:36 +00:00
7b3ba53e97 Ajout de la prise en charge des photos de profil 2026-08-31 15:17:38 +00:00
cb4470e481 clean: remove pycache from tracking 2026-08-31 14:46:54 +00:00
dfb262809a Ajout de dependance 2026-08-31 14:42:26 +00:00
8f91a0ec87 Config env 2026-08-31 14:41:28 +00:00
Soriba SYLLA
65ecef52b5 Ajout du contexte processor 2026-08-31 14:26:02 +00:00
13e9d7dd19 Correction: Affichage detail reservation 2026-08-31 14:26:02 +00:00
88de14aabc Implementation de sso 2026-08-31 14:26:02 +00:00
0576443f02 Supprimer gestion_salle/migrations/0001_initial.py 2026-08-31 14:26:02 +00:00
e306b1d67d Supprimer gestion_projet/migrations/0001_initial.py 2026-08-31 14:26:02 +00:00
f4663c6429 Supprimer gestion_employe/migrations/0001_initial.py 2026-08-31 14:26:02 +00:00
6497d5ce90 Supprimer gestion_conge/migrations/0001_initial.py 2026-08-31 14:26:02 +00:00
a10d52e2df Correction enregistrement profil 2026-08-31 14:25:59 +00:00
2a3a330d38 Affichage de la liste des contrat d'un employé 2026-08-31 14:25:13 +00:00
b4fb5bbd91 Correction sur l'affichage des detail de la reservation 2026-08-31 14:25:13 +00:00
098097ea54 Correction modification profil employé 2026-08-31 14:25:13 +00:00
3abdf9ad6f Filtrage des reservations par salle 2026-08-31 14:25:13 +00:00
4338204f48 Bug: Correction du refus des reservations et d'envoi de mail 2026-08-31 14:25:11 +00:00
ac4e21c336 Bug: Validation des conges 2026-08-31 14:22:38 +00:00
c51ea4d334 Bug: Validation des conges 2026-08-31 14:22:34 +00:00
b88a251694 Correction Bug : Validation 2026-08-31 14:20:11 +00:00
15b48d3db6 Bug: Validation de reservation 2026-08-31 14:20:11 +00:00
c69cf6c8f3 feature: Affichage de tous les detail de la resevation 2026-08-31 14:20:11 +00:00
8456a39226 feature: Affichage du message sur l'tat du contrat 2026-08-31 14:20:11 +00:00
bb4d47c6b1 feature: Affichage des details de la reservation 2026-08-31 14:20:11 +00:00
d721af865a Fonctionnalite : Ajout de la liste des contrats 2026-08-31 14:20:08 +00:00
fd74c06251 Fonctionnalite: Ajout des contrats 2026-08-31 13:25:53 +00:00
be357aefa5 Bug: Affichage de la lite des conges en fonction des profil 2026-08-31 13:25:51 +00:00
e06ee9a434 Bug: Affichage des details de la reservation 2026-08-31 13:24:36 +00:00
a89fdd7216 Bug : Affichages des conges 2026-08-31 13:19:46 +00:00
955083da2b Bug : Affichage des demandes de conges 2026-08-31 13:11:37 +00:00
f6715d62d3 Bug: Details des reservations 2026-08-31 13:02:12 +00:00
17f0b4f8dc Gestion de l'envoie des mails 2026-08-31 13:00:12 +00:00
e5309927a5 Fonctionnalite: verifier qu'aucun contrat n'est actif avant la creation d'un nouveau pour un employe 2026-08-31 12:58:07 +00:00
16b4489088 Verification du statut avant la creation d'un contrat 2026-08-31 12:55:00 +00:00
117bbe255b clean: suppression de pycache du tracking 2026-08-31 12:17:42 +00:00
c35cfc50d4 Prise en charge de la photo de profil 2026-08-31 12:17:23 +00:00
d464d19981 Prise en charge de la photo de profil 2026-08-31 12:16:32 +00:00
f7ce6065ac Prise en charge de la photo de profil 2026-08-31 12:16:32 +00:00
9a44d5440a Prise en charge de la photo de profil 2026-08-31 12:16:32 +00:00
cad7c94541 Ajout de la prise en charge des photos de profil 2026-08-31 12:16:23 +00:00
ef86e03c37 clean: suppression de pycache du tracking 2026-08-31 12:15:23 +00:00
c14170caef Finalisation du Jenkinsfile 2026-08-31 12:13:43 +00:00
559158257e Deploiement automatique 2026-08-31 12:13:43 +00:00
a916e12584 Ajout de dependance 2026-08-31 12:13:43 +00:00
63543143c5 Config des environnement 2026-08-31 12:13:43 +00:00
12b0390306 Config env 2026-08-31 12:13:34 +00:00
8478262c80 Derniere modification du Jenkinsfile 2026-08-31 12:11:21 +00:00
078f164c8b utilisation des credentials 2026-08-31 12:11:21 +00:00
39d9dc810f pipeline9 2026-08-31 12:11:21 +00:00
b14ec02434 pipeline 8 2026-08-31 12:11:21 +00:00
6bb978ab7e pipeline 7 2026-08-31 12:11:21 +00:00
81e8fa3901 pipeline 6 2026-08-31 12:11:20 +00:00
2948f12b89 pipeline 5 2026-08-31 12:11:20 +00:00
b88a21dc5d pipeline 4 2026-08-31 12:11:20 +00:00
486ba88885 pipeline3 2026-08-31 12:11:20 +00:00
5fcc163dae pipeline 2 2026-08-31 12:11:20 +00:00
6857c8b4ac Pipeline 1 2026-08-31 12:11:20 +00:00
31389e677e Test pipeline 2026-08-31 12:11:20 +00:00
500479fd46 Ajout de l'agent 2026-08-31 12:11:20 +00:00
b3ead5eb73 jenkins test 3 2026-08-31 12:11:20 +00:00
d9670dd1b3 renommage jenkinsfile 2026-08-31 12:11:20 +00:00
f65ac75710 Test jenkins 1 2026-08-31 12:11:20 +00:00
741519c341 feature: Affichage de la liste des bailleur 2026-04-30 14:16:42 +02:00
48f069506a feature: Affichage des details de la reservation 2026-04-30 13:59:00 +02:00
3693ef29f9 feature: statut creation contrat 2026-04-30 13:33:39 +02:00
c28b14fb98 clean: remove pycache from tracking 2026-04-30 13:28:57 +02:00
29a93e9bfe clean: remove pycache from tracking 2026-04-30 13:28:53 +02:00
a97c233ddb feature: ajout photo de profil 2026-04-30 13:21:16 +02:00
83 changed files with 824 additions and 313 deletions

3
.gitignore vendored
View File

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

3
Jenkinsfile vendored
View File

@@ -2,7 +2,6 @@ pipeline
{
agent any
options {
// This is required if you want to clean before build
skipDefaultCheckout(true)
}
@@ -17,7 +16,7 @@ pipeline
{
steps
{
sh 'echo "Debut du pipeline"'
sh 'echo "Debut du pipeline test"'
cleanWs()
checkout scm
}

Binary file not shown.

View File

@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
import os
from pathlib import Path
from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -24,7 +25,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=True, cast=bool)
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
#CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
@@ -78,7 +79,6 @@ WSGI_APPLICATION = 'SIRH.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
@@ -88,7 +88,7 @@ DATABASES = {
'HOST': config("DB_HOST"),
'PORT': config("DB_PORT", cast=int),
}
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators

View File

@@ -1,8 +1,9 @@
# SGL/sso.py
from gestion_employe.models import Employe, Departement
from simple_sso.sso_client.client import Client
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from simple_sso.sso_client.client import Client
from gestion_employe.models import Departement, Employe
User = get_user_model()
@@ -11,8 +12,6 @@ class SIRHClient(Client):
"""Crée le User et son Profile avec les infos reçues du serveur."""
user_data_copy = user_data.copy()
print("Un petit test", user_data_copy)
user_data.pop("departement", None)
user_data.pop("chef_departement", None)
user_data.pop("groups", None)

View File

@@ -14,8 +14,8 @@
</head>
<body>
<div class="container-fluid">
<div class="row flex-nowrap">
{% include 'parts/menu_application.html' %}
<div class="row flex-nowrap">
{% include 'parts/menu_principal.html' %}
<div class="col p-4">
{% block 'contenu' %} {% endblock %}

View File

@@ -1,8 +1,24 @@
<div class="col-1 bg-danger border d-flex flex-column vh-100 pt-5 sticky-top">
<a href="{{ SIRH_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-people-fill fs-1 d-block"></i>SIRH
{% load static %}
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<img src="{% static 'img/cerfig.jpg' %}" alt="Logo" width="85" height="30" class="d-inline-block align-text-top">
</a>
<a href="{{ SGL_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-building-gear fs-1 d-block"></i> SGL
</a>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="nav nav-tabs justify-content-end w-100">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ SIRH_LIEN }}">SIRH</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ SGL_LIEN }}">SGL</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Laboratoire</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@@ -14,10 +14,10 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
# from django.conf.urls.static import static
from django.conf import settings
from . import views
from .sso import SIRHClient
@@ -59,4 +59,3 @@ urlpatterns = [
),
path("client/", include(client_sirh.get_urls())),
]
# + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -2,8 +2,6 @@ from django.contrib.auth import logout
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.conf import settings
def connexion(request):
"""Redirige vers le serveur SSO pour l'authentification."""
@@ -12,5 +10,4 @@ def connexion(request):
def deconnexion(request):
"""Gérez la déconnexion des utilisateurs."""
logout(request)
#return redirect(settings.SSO_LOGOUT_URL)
return redirect("http://authentification.cerfig.org/login")

Binary file not shown.

View File

@@ -1,7 +1,9 @@
from django.conf import settings
from django.core.mail import send_mail
from django.utils import timezone
from gestion_employe.models import Contrat, Employe
from gestion_conge.models import Conge
from gestion_conge.models import Conge
from gestion_employe.models import Contrat, Employe
QUOTA_CONGE_ANNUEL = 30
NOMBRE_PAGINATION = 8
@@ -13,6 +15,8 @@ try:
except:
pass
EMAIL_EQUIPE_INFORMATIQUE = ["support@cerfig.org"]
def solde_conge(employe):
"""Fonction de calcul du solde de congé restant l'employé"""
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()
@@ -38,19 +42,6 @@ def solde_conge(employe):
"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:
@@ -59,3 +50,14 @@ def destinataire_mail_demande_conges(employe):
if employe.departement:
chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True)
return list(chefs_departement.values_list('user__email', flat=True))
def envoyer_mail(sujet, message, destinataires):
"""Fonction d'envoi de mail"""
send_mail(
sujet,
message,
settings.EMAIL_HOST_USER,
destinataires,
fail_silently=False,
)

View File

@@ -1,11 +1,13 @@
from django import forms
from .models import Conge
class CongeForm(forms.ModelForm):
"""Formulaire de demande de congé."""
class Meta:
model = Conge
fields =['type', 'date_debut', 'date_fin']
fields =('type', 'date_debut', 'date_fin')
widgets = {
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-06-16 11:06
# Generated by Django 5.2.13 on 2026-09-03 14:33
import django.db.models.deletion
from django.db import migrations, models

View File

@@ -1,19 +1,14 @@
import pandas as pd
from django.db import models
from gestion_employe.models import Employe
class Conge(models.Model):
"""Modèle de création des congés"""
TYPE_CHOICES = [
# ('maladie', 'Maladie'),
TYPE_CHOICES = (
('conge_annuel', 'Conge Annuel'),
# ('conge_maternite', 'Conge Maternité'),
# ('conge_mariage', 'Conge Mariage'),
# ('conge_naissance', 'Conge de Naissance'),
# ('conge_deces_proche', 'Conge de décès d\'un proche'),
# ('conge_mariage_proche', 'Conge de mariage d\'un proche'),
# ('autre', 'Autre'),
]
)
employe = models.ForeignKey(
Employe,

View File

@@ -3,7 +3,6 @@ const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
// layout : "fitColumns",
columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{

View File

@@ -1,16 +1,20 @@
import json
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.forms.models import model_to_dict
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone
from fonction_utilitaire import fonctions_utilitaire
from gestion_conge.forms import CongeForm
from gestion_employe.models import Affectation, Employe
from django.forms.models import model_to_dict
from django.utils import timezone
from django.db.models import Q
from fonction_utilitaire import fonctions_utilitaire
from .models import Conge
@login_required
def index(request):
"""Vue de gestion de l'index"""
@@ -115,8 +119,7 @@ def demander_conge(request):
conge_obj = form.save(commit=False)
conge_obj.employe = employe
if conge_obj.type == "conge_annuel":
if retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
if conge_obj.type == "conge_annuel" and retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
messages.error(request, "Quota annuel dépassé (30 jours max).")
return redirect("gestion_conges:conge")
@@ -126,7 +129,7 @@ def demander_conge(request):
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]
destinataires = fonctions_utilitaire.destinataire_mail_demande_conges(employe)
)
return redirect("gestion_conges:conge")
@@ -219,13 +222,13 @@ def validation_de_conge(request):
motif_refus = request_data.get("motif_refus", "")
if validation_hierarchique is not None:
conge.validation_hierarchique = True if validation_hierarchique == "valide" else False
conge.validation_hierarchique = validation_hierarchique == "valide"
if validation_hierarchique == "refuse" and not motif_refus:
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
conge.motif_refus = motif_refus if validation_hierarchique == "refuse" else ""
if validation_direction is not None:
conge.validation_direction = True if validation_direction == "valide" else False
conge.validation_direction = validation_direction == "valide"
if validation_direction == "refuse" and not motif_refus:
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
conge.motif_refus = motif_refus if validation_direction == "refuse" else ""

View File

@@ -1,6 +1,8 @@
from django.contrib import admin
from .models import Departement, Employe
@admin.register(Departement)
class DepartementAdmin(admin.ModelAdmin):
list_display = ("nom", )

View File

@@ -1,5 +1,8 @@
from django import forms
from .models import Contrat, Departement, Employe,Affectation,Formation
from .models import Affectation, Contrat, Departement, Employe, Formation
class EmployeForm(forms.ModelForm):
"""Formulaire pour modifier les informations de profil d'un employé"""
class Meta:
@@ -49,7 +52,7 @@ class AffectationForm(forms.ModelForm):
class ContratForm(forms.ModelForm):
class Meta:
model = Contrat
fields = [
fields = (
"numero_contrat",
"type_contrat",
"date_debut",
@@ -57,7 +60,7 @@ class ContratForm(forms.ModelForm):
"salaire_mensuel",
"statut",
"fichier_contrat"
]
)
widgets = {
'numero_contrat': forms.TextInput(attrs={'class': 'form-control'}),
@@ -72,19 +75,19 @@ class ContratForm(forms.ModelForm):
class DepartementForm(forms.ModelForm):
class Meta:
model = Departement
fields = ['nom']
fields = ('nom', )
class FormationForm(forms.ModelForm):
class Meta:
model = Formation
fields = [
fields = (
'titre',
'organisme',
'date_obtention',
'date_fin',
'description',
'certificat'
]
)
widgets = {
'titre': forms.TextInput(attrs={"class": "form-control"}),

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-06-16 11:06
# Generated by Django 5.2.13 on 2026-09-03 14:33
import django.db.models.deletion
from django.conf import settings

View File

@@ -1,8 +1,10 @@
from django.db import models
from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
from gestion_projet.models import Projet
class Departement(models.Model):
"""Modèle représentant un département de l'entreprise."""
nom = models.CharField(max_length=100)
@@ -12,7 +14,7 @@ class Departement(models.Model):
class Employe(models.Model):
"""Modèle représentant un employé de l'entreprise."""
FONCTION_LISTE = [
FONCTION_LISTE = (
('directeur', 'Directeur'),
('assistant_direction', 'Assistante de direction'),
('assistant_technique_recherche', 'Assistant technique de recherche'),
@@ -24,7 +26,7 @@ class Employe(models.Model):
('qualiticien', 'Qualiticien'),
('technicien_surface', 'Technicien de surface'),
('chauffeur', 'Chauffeur'),
]
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
matricule = models.CharField(max_length=10, unique=True, null=True, blank=True)
@@ -52,20 +54,21 @@ class Employe(models.Model):
class Contrat(models.Model):
"""Modèle représentant un contrat de travail d'un employé."""
TYPE_CONTRAT = [
TYPE_CONTRAT = (
('contrat_duree_determinee', 'Contrat à Durée Déterminée'),
('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'),
('contrat_prestation', 'Contrat de Prestation de Service'),
('contrat_stage', 'Contrat de Stage'),
('convention_bourse_entretien', 'Convention de bourse d\'entretien'),
]
)
STATUT_CONTRAT = [
STATUT_CONTRAT = (
('actif', 'Actif'),
('termine', 'Terminé'),
('suspendu', 'Suspendu'),
('rupture_contrat', 'Rupture de Contrat'),
]
)
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
numero_contrat = models.CharField(max_length=100, unique=True)
type_contrat = models.CharField(
@@ -89,7 +92,7 @@ class Contrat(models.Model):
class Affectation(models.Model):
"""Modèle représentant l'affectation d'un employé à un projet avec un rôle spécifique."""
ROLE_CHOICES = [
ROLE_CHOICES = (
('chef_projet', 'Chef de projet'),
('doctorant','Doctorant'),
('mastorant','Mastorant'),
@@ -98,7 +101,7 @@ class Affectation(models.Model):
('laborantin','Laborantin'),
('medecin','Médecin'),
('autre','Autre'),
]
)
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
projet = models.ForeignKey(Projet, on_delete=models.CASCADE)

View File

@@ -12,6 +12,7 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe,
pagination: true,
paginationSize: 10,
responsiveLayout : "hide"
})
fetch(url_liste_employe)
@@ -281,3 +282,72 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
],
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
})
let tabContrats = null;
document.getElementById("modalContratsEmploye")
.addEventListener("show.bs.modal", function () {
const employeId = document.getElementById("detail-id").value;
console.log("Employe ID :", employeId);
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,39 +13,40 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy",
}},
{"title": "Fichier", "field": "certificat", formatter:"link", formatterParams:{
label:"Voir le fichier",
target:"_blank",
}},
],
ajaxURL: url_certificat,
})
const enregistrerProfil = $("enregistrerProfil");
// enregistrerProfil.addEventListener("click", (e) => {
enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
// 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);
const photoInput = $("photo");
if (photoInput.files.length > 0) {
formData.append("photo", photoInput.files[0]);
}
// 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);
// console.log("---------------------------------------------")
// console.log("Données du formulaire:", url);
// console.log("Données du formulaire:", formData);
// fetch(url, {
// method: "POST",
// headers: {
// "X-CSRFToken": csrftoken
// },
// body: formData
// })
// .then(response => response.json())
// .then(data => alert(data.message))
// .catch(error => console.error("Erreur:", error));
// });
fetch(url, {
method: "POST",
headers: {
"X-CSRFToken": csrftoken
},
body: formData
})
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => console.error("Erreur:", error));
});

View File

@@ -12,19 +12,20 @@
</div>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show mt-2" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% if not expiration_contrat %}
<div class="alert alert-danger fade show alert-dismissible mt-2">
{% if not has_contrat %}
<div class="alert alert-danger mt-2">
<strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines.
</div>
{% elif contrat_nb_jours_restant %}
<div class="alert alert-danger fade show alert-dismissible mt-2">
{% elif expiration_contrat %}
<div class="alert alert-warning mt-2">
<strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
</div>
{% endif %}

View File

@@ -0,0 +1,17 @@
<div class="modal fade" id="modalContratsEmploye" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
Contrats de <span id="nom-employe-contrat"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="table-contrats-tabulator"></div>
</div>
</div>
</div>
</div>

View File

@@ -8,7 +8,7 @@
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="modalDetailEmployeBody" data-url="{% url 'gestion_employe:enregistrer-detail-employe' %}" data-url-departements="{% url 'gestion_employe:liste-departements' %}">
<div class="modal-body" id="modalDetailEmployeBody">
<div class="row g-3">
<input type="hidden" id="detail-id">
<div class="col-md-6">
@@ -98,9 +98,6 @@
</h5>
{% if user|has_group:"ressource_humaine" %}
<div>
<button class="btn btn-secondary" disabled>
<i class="bi bi-file-earmark-lock"></i> Contrat actif
</button>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalContrat{{ item.employe.id }}">
<i class="bi bi-file-earmark-text"></i> Créer contrat
</button>

View File

@@ -6,7 +6,7 @@
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}">
<form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}" id="formulaire_mot_de_passe">
{% csrf_token %}
<div class="form-group mb-2">
<label>Votre mot de passe actuel :</label>

View File

@@ -6,6 +6,7 @@ la gestion de l'accès des utilisateurs aux différentes pages.
from django import template
from django.utils import timezone
from gestion_employe.models import Affectation
register = template.Library()

View File

@@ -1,6 +1,7 @@
from . import views
from django.urls import path
from . import views
app_name = 'gestion_employe'
urlpatterns = [
@@ -29,11 +30,17 @@ urlpatterns = [
views.creation_contrat,
name='creation-contrat'
),
path(
'employe/<int:employe_id>/contrats/'
, views.contrats_employe,
name="contrats-employe"
),
path(
'contrat/supprimer/',
views.suppression_contrat,
name='supprimer-contrat'
),
path(
'Affectation/affectation/',
views.affecter_employe_projet,
@@ -83,10 +90,5 @@ urlpatterns = [
"liste-contrat-expirants",
views.liste_contrat_expirants,
name='liste-contrat-expirants'
),
path(
"liste-departements/",
views.liste_departements,
name='liste-departements'
)
]

View File

@@ -1,25 +1,44 @@
import json
from datetime import date, timedelta, datetime
from datetime import datetime, timedelta
import requests
from dateutil.relativedelta import relativedelta
from django.utils import timezone
from decouple import config
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.forms.models import model_to_dict
from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.db.models import Sum
from django.forms.models import model_to_dict
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone
from .models import Employe, Contrat, Affectation, Formation, Departement
from .forms import AffectationForm, ContratForm, FormationForm
from fonction_utilitaire import fonctions_utilitaire
from .forms import AffectationForm, ContratForm, FormationForm
from .models import Affectation, Contrat, Departement, Employe, Formation
@login_required
def index(request):
"""Vue d'index"""
if not request.user.groups.filter(name="ressource_humaine").exists():
return redirect('gestion_employe:mon-profil')
response = requests.get(f"{config('SERVEUR_AUTHENTIFICATION')}recuperer-list-departement/")
if response.status_code == 200:
try:
departements_data = response.json()
for dept in departements_data:
Departement.objects.update_or_create(
nom = dept['nom']
)
except json.JSONDecodeError as e:
print(f"[ERROR] Erreur lors du décodage JSON : {e}")
employes = Employe.objects.all().order_by('-user__date_joined')
nombre_employes = Employe.objects.count()
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
@@ -95,7 +114,7 @@ def affecter_employe_projet(request):
elif total_affectation + temps_nouveau > 100:
messages.warning(
request,
f"Les pourcentages d'affectation de l'employé {employe.first_name} {employe.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
f"Les pourcentages d'affectation de l'employé {employe.user.first_name} {employe.user.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
)
return redirect('gestion_employe:index')
@@ -130,13 +149,17 @@ def mon_profil(request):
try:
employe = Employe.objects.get(user__username=request.user)
except Employe.DoesNotExist:
messages.error(request, "Impossible d'acceder au menu 'Mon profil' car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'Administrateur.")
messages.error(
request,
"Impossible d'acceder au menu 'Mon profil' car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'Administrateur."
)
return redirect("gestion_conges:conge")
contrats = Contrat.objects.filter(employe=employe, statut='actif').first()
projets = Affectation.objects.filter(
employe = employe,
date_fin_daffectation__gte = timezone.now().date()
employe=employe,
date_fin_daffectation__gte=timezone.now().date()
).select_related('projet')
return render(
@@ -144,12 +167,14 @@ def mon_profil(request):
'gestion_employe/monprofil.html',
{
'employe': employe,
'contrats': [{
**model_to_dict(contrats),
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(contrats.type_contrat),
"statut": dict(Contrat.STATUT_CONTRAT).get(contrats.statut),
"fichier_contrat": contrats.fichier_contrat.url if contrats.fichier_contrat else "",
} if contrats else []],
}] if contrats else [],
'projets': [
{
**model_to_dict(a.projet),
@@ -159,32 +184,14 @@ def mon_profil(request):
"pourcentage_temps_affectation": a.pourcentage_temps_affectation
} for a in projets
],
"formation_form": FormationForm(),
"has_contrat": contrats is not None,
"expiration_contrat": contrats.nombre_jours_restant <= fonctions_utilitaire.DUREE_FIN_CONTRAT if contrats else False,
"contrat_nb_jours_restant": contrats.nombre_jours_restant if contrats else None
"contrat_nb_jours_restant": max(0, contrats.nombre_jours_restant if contrats else 0)
}
)
@login_required
def modifier_mot_passe(request):
"""Vue pour permettre à un utilisateur de modifier son mot de passe et ses informations de profil"""
user = User.objects.get(username=request.user)
if request.method == "POST":
ancien_mdp = request.POST["ancien-mdp"]
nouveau_mdp = request.POST["nouveau-mdp"]
confirmation_mdp = request.POST["confirmation-mdp"]
if authenticate(request, username=request.user, password=ancien_mdp) is None:
messages.error(request, "Ancien mot de passe incorrect.")
elif nouveau_mdp != confirmation_mdp:
messages.error(request, "Les deux nouveaux ne correspondent pas.")
else:
user.set_password(nouveau_mdp)
user.save()
messages.success(request, "Mot de passe modifié avec succès.")
return redirect("gestion_employe:mon-profil")
def modifier_employer(request):
"""Vue pour permettre à un utilisateur de modifier les informations d'un employé"""
try:
@@ -212,7 +219,6 @@ def modifier_employer(request):
messages.success(request, "Profil mis à jour avec succès.")
return redirect("gestion_employe:mon-profil")
def enregistrement_document(request):
"""Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil"""
employe = Employe.objects.get(user=request.user)
@@ -263,9 +269,10 @@ def creation_contrat(request):
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()
date_fin__gte=timezone.now().date()
).exists()
if request.method == "POST":
@@ -291,6 +298,45 @@ def creation_contrat(request):
'contrat_form': form
})
@login_required
def contrats_employe(request, employe_id):
try:
employe = Employe.objects.get(id=employe_id)
except Employe.DoesNotExist:
return JsonResponse(
{"error": "Employé non trouvé"},
status=404
)
contrats = Contrat.objects.filter(
employe=employe
).order_by("-date_debut")
data = [
{
"id": contrat.id,
"numero_contrat": contrat.numero_contrat,
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(
contrat.type_contrat
),
"date_debut": contrat.date_debut.strftime("%Y-%m-%d"),
"date_fin": contrat.date_fin.strftime("%Y-%m-%d") if contrat.date_fin else "",
"salaire_mensuel": contrat.salaire_mensuel,
"statut": dict(Contrat.STATUT_CONTRAT).get(
contrat.statut
),
"fichier": contrat.fichier_contrat.url if contrat.fichier_contrat else "",
}
for contrat in contrats
]
return JsonResponse({
"employe": employe.user.get_full_name(),
"contrats": data
})
@login_required
def enregistrer_detail_employe(request):
"""Vue pour permettre à un utilisateur de modifier les détails d'un employé via une requête AJAX"""
@@ -459,14 +505,46 @@ def supprimer_formation(request, id_formation):
messages.success(request, "Formation supprimée ")
return redirect("mes_formations")
def liste_departements(request):
"""Vue pour retourner la liste de tous les départements en JSON"""
departements = Departement.objects.all()
data = [
{
"id": dept.id,
"nom": dept.nom
@login_required
def modifier_mot_passe(request):
"""Vue pour permettre à un employé de modifier son mot de passe"""
if request.method == "POST":
username = request.user.username
ancien_mdp = request.POST.get("ancien-mdp")
nouveau_mdp = request.POST.get("nouveau-mdp")
confirmation_mdp = request.POST.get("confirmation-mdp")
try:
validate_password(nouveau_mdp, user=request.user)
except ValidationError as e:
messages.error(request, f"Erreur : {' '.join(e.messages)}")
return redirect("gestion_employe:mon-profil")
donnees = {
"username": username,
"ancien_mdp": ancien_mdp,
"nouveau_mdp": nouveau_mdp,
"confirmation_mdp": confirmation_mdp
}
for dept in departements
]
return JsonResponse(data, safe=False)
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
response = requests.post(
f"{config('SERVEUR_AUTHENTIFICATION')}modifier-mot-de-passe/",
json=donnees,
headers=headers
)
data = response.json()
cle = next(iter(data.keys()))
valeur = next(iter(data.values()))
if cle == "success":
messages.success(request, valeur)
else:
messages.error(request, valeur)
return redirect("gestion_employe:mon-profil")

View File

@@ -1,14 +1,16 @@
from django import forms
from gestion_projet.models import Projet
from .models import (
ActiviteProjet,
Bailleur,
DocumentProjet,
FinancementProjet,
LivrablesLivres,
DomaineDeRecherche
)
class ProjetForm(forms.ModelForm):
"""Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs."""
class Meta:

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-06-16 11:06
# Generated by Django 5.2.13 on 2026-09-03 14:33
import django.db.models.deletion
from django.db import migrations, models

View File

@@ -1,7 +1,9 @@
from datetime import datetime
from django.db import models
from datetime import date
from django.utils import timezone
class Bailleur(models.Model):
"""Modèle représentant un bailleur de fonds pour les projets de recherche."""
nom_organisme = models.CharField(
@@ -29,12 +31,12 @@ class Bailleur(models.Model):
class DomaineDeRecherche(models.Model):
"""Modèle représentant les domaines de recherche"""
DOMAINE_RECHERCHE = [
DOMAINE_RECHERCHE = (
('sciences_sociales', 'Sciences sociales'),
('naturelles', 'Naturelles'),
('humaines', 'Humaines'),
('veterinaires', 'Vétérinaires')
]
)
nom = models.CharField(
max_length=100,
@@ -52,13 +54,14 @@ class DomaineDeRecherche(models.Model):
class Projet(models.Model):
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
TYPE_PROJET = [
TYPE_PROJET = (
('laboratoire', 'Laboratoire'),
('épidémiologie', 'Épidémiologie'),
('sciences sociales', 'Sciences sociales'),
('cliniques', 'Cliniques'),
('autre', 'Autre'),
]
)
id_projet = models.CharField(
max_length=100,
blank=True,
@@ -111,13 +114,13 @@ class Projet(models.Model):
@property
def statut(self):
if self.date_fin < date.today():
if self.date_fin < datetime.date.today():
return "Terminé"
return "En cours"
@property
def avancement(self):
aujourd_hui = date.today()
aujourd_hui = datetime.date.today()
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
duree_projet = (self.date_fin - self.date_debut).days
temps_ecoule = (aujourd_hui - self.date_debut).days
@@ -155,7 +158,7 @@ class FinancementProjet(models.Model):
class DocumentProjet(models.Model):
"""Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché."""
NOM_DOCUMENT_CHOICES = [
NOM_DOCUMENT_CHOICES = (
('protocole', 'Protocole d’étude'),
('ethique', "Approbation du comité d'éthique"),
('autorisation', 'Autorisation (DNLP)'),
@@ -165,7 +168,7 @@ class DocumentProjet(models.Model):
('convention', 'Convention'),
('rapport_final', 'Rapport final'),
('autre', 'Autre'),
]
)
projet = models.ForeignKey(
Projet,

View File

@@ -23,9 +23,8 @@ btnEnregistrerBailleur.addEventListener('click', function() {
});
});
document.addEventListener("DOMContentLoaded", function () {
table = new Tabulator("#table-bailleurs", {
table = new Tabulator("#table-bailleurs", {
ajaxURL: "/gestion-projet/bailleurs/",
layout: "fitColumns",
pagination: "local",
@@ -45,7 +44,4 @@ document.addEventListener("DOMContentLoaded", function () {
supprimerBailleur(data.id);
}
}
});
});

View File

@@ -90,12 +90,6 @@ urlpatterns = [
views.liste_activites_projet,
name='liste-activites-projet'
),
# path(
# 'projet/ajout-de-document/',
# views.ajouter_document_projet,
# name='ajouter-document'
# ),
path(
'projet/liste-des-documents/',
views.liste_documents_projet,

View File

@@ -1,30 +1,34 @@
from datetime import date
import datetime
from decimal import Decimal, InvalidOperation
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Sum
from django.forms.models import model_to_dict
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone
from gestion_employe.forms import AffectationForm
from gestion_employe.models import Affectation, Employe
from gestion_projet.forms import ProjetForm
from gestion_projet.models import Projet
from .models import (
DocumentProjet,
Bailleur,
FinancementProjet,
ActiviteProjet,
LivrablesLivres,
)
from .forms import (
ActiviteProjetForm,
BailleurForm,
DocumentProjetForm,
FinancementProjetFrom,
BailleurForm,
LivrablesLivresForm
LivrablesLivresForm,
)
from .models import (
ActiviteProjet,
Bailleur,
DocumentProjet,
FinancementProjet,
LivrablesLivres,
)
def liste_projet(request):
""" Vue pour retourner la liste de tous les projet """
@@ -88,7 +92,7 @@ def index(request):
'nombre_personnel': nombre_personnel,
'budget_total': budget_total,
'formulaire_creation_projet': ProjetForm(),
'projet_en_cours': Projet.objects.filter(date_fin__gte=date.today()).count(),
'projet_en_cours': Projet.objects.filter(date_fin__gte=datetime.datetime.now(tz=datetime.timezone.utc).date()).count(),
}
return render(request, 'gestion_projet/index.html', context)
@@ -137,8 +141,7 @@ def mises_a_jour_projet(request):
@login_required
def creation_bailleur(request):
form = BailleurForm(request.POST)
if request.method == 'POST':
if form.is_valid():
if request.method == 'POST' and form.is_valid():
form.save()
return JsonResponse({'success': True})
return JsonResponse({'success': False})

View File

@@ -1,10 +1,12 @@
from django import forms
from .models import Reservation
class ReservationForm(forms.ModelForm):
class Meta:
model = Reservation
fields = ['salle', 'date_debut', 'date_fin', 'heure_debut', 'heure_fin', 'motif_reservation', 'besoin_zoom', 'besoin_ordi']
fields = ('salle', 'date_debut', 'date_fin', 'heure_debut', 'heure_fin', 'motif_reservation', 'besoin_zoom', 'besoin_ordi')
widgets = {
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-06-16 11:06
# Generated by Django 5.2.13 on 2026-09-03 14:33
import django.db.models.deletion
from django.db import migrations, models

View File

@@ -1,20 +1,21 @@
from django.db import models
from gestion_employe.models import Employe
class Reservation(models.Model):
"""Modèle de création des réservations"""
TYPE_CHOICES = [
TYPE_CHOICES = (
('formation', 'Salle de formation'),
('reunion', 'Salle de réunion'),
('lien_zoom', 'Lien Zoom'),
]
STATUT = [
)
STATUT = (
('en_attente', 'En attente'),
('validee', 'Validée'),
('refusee', 'Refusée'),
('annulee', 'Annulée'),
]
)
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
salle = models.CharField(max_length=100, choices=TYPE_CHOICES)

View File

@@ -26,7 +26,7 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
$("date_debut").value=data.date_debut;
$("date_evenement").value=data.date_evenement;
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
@@ -34,6 +34,12 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom;
const zoomContainer = $("lien_zoom_container");
if (!data.besoin_zoom) {
zoomContainer.classList.add("d-none");
} else {
zoomContainer.classList.remove("d-none");
}
if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none";
}
@@ -81,6 +87,8 @@ document.addEventListener("DOMContentLoaded", function () {
})
$("bouton-annuler").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler;
@@ -135,6 +143,8 @@ if($("ajoutZoom")){
if($("refuserReservation")){
$("refuserReservation").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value;
@@ -161,7 +171,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
columns: [
{title: "Employé", field: "employe"},
{title: "Salle", field: "salle"},
{title: "Date de l'evenement", field: "date_debut"},
{title: "Date de l'evenement", field: "date_evenement"},
{title: "Heure de début", field: "heure_debut"},
{title: "Heure de fin", field: "heure_fin"},
{title: "Motif de reservation", field: "motif_reservation"},
@@ -173,11 +183,14 @@ 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';
}
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;
@@ -185,7 +198,7 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
$("date_debut").value = data.date_debut;
$("date_evenement").value = data.date_evenement;
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
@@ -193,6 +206,16 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom;
console.log("Verification de data :", data);
const refuserReservation = document.querySelector("#refuserReservation");
const bouton_valider = document.querySelector("#bouton-valider");
if((refuserReservation | bouton_valider) && data.statut === "En attente"){
refuserReservation.style.display = "inline-block";
bouton_valider.style.display = "inline-block";
}
if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none";
}

View File

@@ -16,7 +16,7 @@
{% endif %}
<div class="row">
<div class="col py-2 px-4">
<h1 class="text-center">Reservation de salle</h1>
<h1 class="text-center">Réservation de salle</h1>
<hr>
<div class="row d-flex justify-content-around">
<div class="form-group col-5 me-2">
@@ -34,7 +34,7 @@
</div>
<div class="row mt-2">
<div class="col d-flex justify-content-center">
<button class="btn btn-lg btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle reservation</button>
<button class="btn btn-lg btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle réservation</button>
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#modalReservationAttente">
Validation en attente <span class="badge badge-light">{{ nb_reservation_attente }}</span>
</button>

View File

@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="modalReservationLabel">
Nouvelle reservation
Nouvelle réservation
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>

View File

@@ -5,14 +5,14 @@
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="modalDetailReservationLabel">
Détails de la reservation (<span id='statut-reservation'></span>)
Détails de la réservation (<span id='statut-reservation'></span>)
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="POST" data-urlannuler="{% url 'gestion_salle:annuler-reservation' %}" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details">
{% csrf_token %}
<input type='hidden' class="form-control" id="id_reservation_detail" name='id_reservation'>
<input type='' class="form-control" id="id_reservation_detail" name='id_reservation' hidden>
<div class="form-group mb-2">
<label>Employé :</label>
<input class="form-control" id="employe" readonly>
@@ -22,13 +22,9 @@
<input class="form-control" id="salle" readonly>
</div>
<div class="form-group mb-2">
<label>Date de debut :</label>
<input type='date' class="form-control" id="date_debut" readonly >
<label>Date de l'évènement :</label>
<input type='date' class="form-control" id="date_evenement" readonly >
</div>
{% comment %} <div class="form-group mb-2">
<label>Date de fin :</label>
<input type='date' class="form-control" id="date_fin" readonly >
</div> {% endcomment %}
<div class="form-group mb-2">
<label>Heure de début :</label>
<input type='time' class="form-control" id="heure_debut" readonly>
@@ -38,7 +34,7 @@
<input type='time' class="form-control" id="heure_fin" readonly >
</div>
<div class="form-group mb-2">
<label>Motif de la reservation :</label>
<label>Motif de la réservation :</label>
<textarea class="form-control" id="motif_reservation" readonly ></textarea>
</div>
<div class="form-check mb-2">
@@ -50,18 +46,19 @@
<input type="url" class="form-control" id="lien_zoom" readonly >
</div>
<div class="form-check mb-2">
<label label="form-check-label">Besoin d'un ordinateur</label>
<label class="form-check-label">Besoin d'un ordinateur {{ reservation.statut }}</label>
<input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly >
</div>
<hr />
<div class='d-flex justify-content-around mt-2'>
{% if appartient_au_departement_informatique %}
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button>
{% endif %}
{% if user|has_group:'direction' %}
{% if appartient_direction %}
<button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
<button class="btn btn-success" id="bouton-valider">Valider</button>
{% endif %}
<span id="current-user-id" data-userid="{{ request.user.id }}"></span>
<span id="current-user-id" data-user-id="{{ request.user.id }}"></span>
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
</div>
</form>

View File

@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="modalReservationAttenteLabel">
Liste des reservation en attente de validation
Liste des réservation en attente de validation
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>

View File

@@ -1,4 +1,5 @@
from django.urls import path
from . import views
app_name = "gestion_salle"

View File

@@ -1,19 +1,22 @@
from datetime import timedelta
import json
from datetime import timedelta
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 django.http import HttpRequest, JsonResponse
from django.shortcuts import redirect, render
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 d'affichage de la page d'accueil de la gestion des salles"""
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
@@ -34,11 +37,11 @@ def index(request: HttpRequest):
besoin_ordi = form.cleaned_data['besoin_ordi']
if date_fin < date_debut:
messages.error(request, "Date fin invalide.")
messages.error(request, "La date de fin est antérieure à la date de début.")
return redirect('gestion_salle:reservation-salle')
if heure_fin <= heure_debut:
messages.error(request, "Heure invalide.")
messages.error(request, "L'heure de fin est antérieure à l'heure de début.")
return redirect('gestion_salle:reservation-salle')
if not request.user.first_name.strip() or not request.user.last_name.strip():
@@ -52,7 +55,6 @@ def index(request: HttpRequest):
current_date = date_debut
while current_date <= date_fin:
reservation = Reservation.objects.create(
employe=employe,
date_debut=current_date,
@@ -71,21 +73,30 @@ def index(request: HttpRequest):
messages.success(request, "Réservation(s) créée(s) avec succès.")
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION and salle != "lien_zoom":
fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle",
message = f"""
message = f"""Bonjour,
Une nouvelle demande de réservation de la {dict(Reservation.TYPE_CHOICES).get(salle)} a été effectuée par {employe.user.first_name} {employe.user.last_name} du {form.cleaned_data.get('date_debut').strftime('%d/%m/%Y')} au {form.cleaned_data.get('date_fin').strftime('%d/%m/%Y')} pour motif "{motif}".
Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires = fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
)
if (salle == "lien_zoom" or besoin_zoom or besoin_ordi) and fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
fonctions_utilitaire.envoyer_mail(
sujet="Demande de lien Zoom",
message=f"""Bonjour,
Une nouvelle demande de réservation de salle nécessitant un lien Zoom a été effectuée par {employe.user.first_name} {employe.user.last_name} du {date_debut.strftime('%d/%m/%Y')} au {date_fin.strftime('%d/%m/%Y')} de {heure_debut.strftime('%H:%M')} a {heure_fin.strftime('%H:%M')} pour le motif {motif}
Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
)
return redirect('gestion_salle:reservation-salle')
formulaire_reservation = ReservationForm()
departement = employe.departement
appartient_direction = request.user.groups.filter(name='direction').exists()
liste_demande_reservation = Reservation.objects.filter(
employe=employe,
statut='en_attente'
@@ -95,7 +106,7 @@ Veuillez vous connecter à la plateforme pour plus de détails.""",
'formulaire_reservation': formulaire_reservation,
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique",
'appartient_direction': appartient_direction,
'appartient_direction': request.user.groups.filter(name='direction').exists(),
'liste_demande_reservation': list(liste_demande_reservation),
}
@@ -107,6 +118,7 @@ def liste_reservation(request:HttpRequest):
liste_reservation = []
for reservation in reservations:
print(reservation.statut)
color = None
if reservation.statut == "en_attente":
color = "#ffc107"
@@ -137,23 +149,22 @@ def liste_reservation_attente(request):
"employe_id": reservation.employe.user.id,
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
'statut': dict(Reservation.STATUT).get(reservation.statut),
"date_evenement": reservation.date_debut.strftime('%Y-%m-%d'),
} 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,
'salle': dict(Reservation.TYPE_CHOICES).get(reservation.salle),
'statut': dict(Reservation.STATUT).get(reservation.statut),
'date_debut': reservation.date_debut.strftime('%Y-%m-%d'),
'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'),
'heure_debut': reservation.heure_debut.strftime('%H:%M'),
'heure_fin': reservation.heure_fin.strftime('%H:%M'),
'motif_reservation': reservation.motif_reservation,
@@ -223,7 +234,7 @@ def valider_reservation(request: HttpRequest):
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.""",
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]
)
@@ -231,18 +242,16 @@ def valider_reservation(request: HttpRequest):
return redirect('gestion_salle:reservation-salle')
@login_required
def refuser_reservation(request: HttpRequest):
"""Refuser une réservation"""
if request.method == 'POST':
reservation_id = json.loads(request.body).get('id_reservation')
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:
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'
return JsonResponse({"message": "La resevertion selectionné n'existe pas."})
else:
reservation.statut = "refusee"
reservation.save()
fonctions_utilitaire.envoyer_mail(

View File

@@ -3,7 +3,23 @@ const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
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: [
{"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
@@ -15,7 +31,23 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
outputFormat:"dd/MM/yy",
}},
{"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"},
>>>>>>> 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 :{
allowEmpty : true ,
}},
@@ -24,7 +56,23 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}},
],
pagination: true,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
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");

View File

@@ -12,7 +12,20 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe,
pagination: true,
paginationSize: 10,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
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)

View File

@@ -16,6 +16,10 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
],
ajaxURL: url_certificat,
})
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
@@ -44,3 +48,52 @@ enregistrerProfil.addEventListener("click", (e) => {
.then(data => alert(data.message))
.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');
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
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() {
const form = document.getElementById('formBailleur');
@@ -21,6 +34,12 @@ btnEnregistrerBailleur.addEventListener('click', function() {
alert('Ce bailleur existe déjà dans la base de données.');
}
});
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
});
document.addEventListener("DOMContentLoaded", function () {
@@ -49,3 +68,15 @@ document.addEventListener("DOMContentLoaded", function () {
});
<<<<<<< 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

@@ -15,6 +15,21 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
.then(response => response.json())
.then(data => {
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_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation;
@@ -29,10 +44,37 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
if (data.besoin_zoom === false){
$("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
})
}
});
@@ -78,11 +120,26 @@ $("bouton-annuler").addEventListener("click", (e) => {
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
console.log("URL d'annulation :");
console.log($("formulaire-details"))
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(
url_annuler,
{
@@ -133,9 +190,24 @@ if($("refuserReservation")){
const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
console.log(idRes);
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, {
method: "POST",
headers: {
@@ -167,11 +239,52 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData();
<<<<<<< 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
if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none';
}
<<<<<<< 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_refus").value = data.id;
$("id_reservation_zoom").value = data.id;
@@ -179,18 +292,53 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
<<<<<<< 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_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;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
if (id_user != data.employe_id){
$("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"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();